diff --git a/configuration/buildAndUpdateProduct.sh b/configuration/buildAndUpdateProduct.sh index 10d10f5197e..5f88ff9dc14 100755 --- a/configuration/buildAndUpdateProduct.sh +++ b/configuration/buildAndUpdateProduct.sh @@ -95,7 +95,7 @@ parallelexecution=0 p2local=0 if [ $# -eq 0 ]; then - echo "buildAndUpdateProduct [-b -u -g -t -a -r -o -c -p -v -m -n -l -x ] [build|install|all|hot-deploy|remote-deploy|local-deploy|release]" + echo "buildAndUpdateProduct [-b -u -g -t -a -r -o -c -p -v -m -n -l -x -j ] [build|install|all|hot-deploy|remote-deploy|local-deploy|release]" echo "" echo "-g Disable GWT compile, no gwt files will be generated, old ones will be preserved." echo "-b Build GWT permutation only for one browser and English language." @@ -115,6 +115,7 @@ if [ $# -eq 0 ]; then echo "-u Run without confirmation messages. Use with extreme care." echo "-v Build local p2 respository, and use this instead of p2.sapsailing.com" echo "-x use this many worker threads for building GWT permutations (default: 2)." + echo "-j only execute the provided test package during tests" echo "" echo "build: builds the server code using Maven to $PROJECT_HOME (log to $START_DIR/build.log)" echo "" @@ -133,7 +134,7 @@ if [ $# -eq 0 ]; then echo "local-deploy: performs deployment of one or more bundles into a local directory" echo "Example: $0 -n com.sap.sailing.www -s /home/user/myserver local-deploy" echo "" - echo "remote-deploy: performs hot deployment of the java code to a remote server" + echo "remote-deploy: deploys the last build results to a remote server and optionally re-starts it" echo "Example: $0 -s dev -w trac@sapsailing.com remote-deploy" echo "" echo "clean: cleans all code and GWT files" @@ -151,7 +152,7 @@ echo SERVERS_HOME is $SERVERS_HOME echo BRANCH is $active_branch echo VERSION is $VERSION_INFO -options=':bgtocparvm:n:l:s:w:x:u' +options=':bgtocparvm:n:l:s:w:x:j:u' while getopts $options option do case $option in @@ -172,6 +173,7 @@ do u) suppress_confirmation=1;; v) p2local=1;; x) GWT_WORKERS=$OPTARG;; + j) TESTCASE_TO_EXECUTE=$OPTARG;; \?) echo "Invalid option" exit 4;; esac @@ -263,7 +265,7 @@ if [[ "$@" == "release" ]]; then cp -v $PROJECT_HOME/java/target/configuration/jetty/etc/jetty-deployer.xml configuration/jetty/etc cp -v $PROJECT_HOME/java/target/configuration/jetty/etc/realm.properties configuration/jetty/etc cp -v $PROJECT_HOME/java/target/configuration/monitoring.properties configuration/ - cp -v $PROJECT_HOME/java/target/configuration/security.properties configuration/ + cp -v $PROJECT_HOME/java/target/configuration/mail.properties configuration/ cp -v $PROJECT_HOME/configuration/mongodb.cfg $ACDIR/ cp -v $PROJECT_HOME/java/target/udpmirror $ACDIR/ cp -v $PROJECT_HOME/java/target/http2udpmirror $ACDIR @@ -539,7 +541,7 @@ if [[ "$@" == "build" ]] || [[ "$@" == "all" ]]; then fi cd $PROJECT_HOME/java - if [ $gwtcompile -eq 1 ]; then + if [ $gwtcompile -eq 1 ] && [[ "$clean" == "clean" ]]; then echo "INFO: Compiling GWT (rm -rf com.sap.$PROJECT_TYPE.gwt.ui/com.sap.$PROJECT_TYPE.*)" rm -rf com.sap.$PROJECT_TYPE.gwt.ui/com.sap.$PROJECT_TYPE.* GWT_XML_FILES=`find com.sap.$PROJECT_TYPE.gwt.ui/src/main/resources -name '*.gwt.xml'` @@ -591,6 +593,11 @@ if [[ "$@" == "build" ]] || [[ "$@" == "all" ]]; then else extra="$extra -DskipTests=false" # TODO: Think about http://maven.apache.org/surefire/maven-surefire-plugin/examples/fork-options-and-parallel-execution.html + if [[ "$TESTCASE_TO_EXECUTE" != "" ]]; then + # http://maven.apache.org/surefire/maven-surefire-plugin/examples/single-test.html + echo "Running only testcase $TESTCASE_TO_EXECUTE" + extra="$extra -Dtest=$TESTCASE_TO_EXECUTE" + fi fi if [ $android -eq 0 ] && [ $gwtcompile -eq 0 ] && [ $testing -eq 0 ]; then @@ -765,7 +772,7 @@ if [[ "$@" == "install" ]] || [[ "$@" == "all" ]]; then if [ ! -f "$ACDIR/env.sh" ]; then cp -v $PROJECT_HOME/java/target/env.sh $ACDIR/ cp -v $PROJECT_HOME/java/target/configuration/monitoring.properties $ACDIR/configuration/ - cp -v $PROJECT_HOME/java/target/configuration/security.properties $ACDIR/configuration/ + cp -v $PROJECT_HOME/java/target/configuration/mail.properties $ACDIR/configuration/ cp -v $PROJECT_HOME/java/target/configuration/logging.properties $ACDIR/configuration/ fi @@ -846,7 +853,7 @@ if [[ "$@" == "remote-deploy" ]]; then $SCP_CMD $PROJECT_HOME/java/target/configuration/jetty/etc/jetty-deployer.xml $REMOTE_SERVER_LOGIN:$REMOTE_SERVER/configuration/jetty/etc $SCP_CMD $PROJECT_HOME/java/target/configuration/jetty/etc/realm.properties $REMOTE_SERVER_LOGIN:$REMOTE_SERVER/configuration/jetty/etc $SCP_CMD $PROJECT_HOME/java/target/configuration/monitoring.properties $REMOTE_SERVER_LOGIN:$REMOTE_SERVER/configuration/ - $SCP_CMD $PROJECT_HOME/java/target/configuration/security.properties $REMOTE_SERVER_LOGIN:$REMOTE_SERVER/configuration/ + $SCP_CMD $PROJECT_HOME/java/target/configuration/mail.properties $REMOTE_SERVER_LOGIN:$REMOTE_SERVER/configuration/ $SCP_CMD $PROJECT_HOME/java/target/env.sh $REMOTE_SERVER_LOGIN:$REMOTE_SERVER/ $SCP_CMD $PROJECT_HOME/java/target/start $REMOTE_SERVER_LOGIN:$REMOTE_SERVER/ diff --git a/java/com.google.gwt.dev/.classpath b/java/com.google.gwt.dev/.classpath new file mode 100755 index 00000000000..d7a0cfed444 --- /dev/null +++ b/java/com.google.gwt.dev/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/java/com.google.gwt.dev/.project b/java/com.google.gwt.dev/.project new file mode 100755 index 00000000000..b4df77e1e2d --- /dev/null +++ b/java/com.google.gwt.dev/.project @@ -0,0 +1,28 @@ + + + com.google.gwt.dev + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/java/com.google.gwt.dev/.settings/org.eclipse.jdt.core.prefs b/java/com.google.gwt.dev/.settings/org.eclipse.jdt.core.prefs new file mode 100755 index 00000000000..295926d9641 --- /dev/null +++ b/java/com.google.gwt.dev/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/java/com.google.gwt.dev/.settings/org.eclipse.pde.core.prefs b/java/com.google.gwt.dev/.settings/org.eclipse.pde.core.prefs new file mode 100755 index 00000000000..b7e72d019c2 --- /dev/null +++ b/java/com.google.gwt.dev/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/java/com.google.gwt.dev/META-INF/MANIFEST.MF b/java/com.google.gwt.dev/META-INF/MANIFEST.MF new file mode 100755 index 00000000000..3dace3e8b85 --- /dev/null +++ b/java/com.google.gwt.dev/META-INF/MANIFEST.MF @@ -0,0 +1,523 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Dev +Bundle-SymbolicName: com.google.gwt.dev +Bundle-Version: 2.7.0 +Bundle-Vendor: GOOGLE +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Bundle-ClassPath: lib/gwt-dev.jar, + . +Export-Package: com.gargoylesoftware.htmlunit, + com.gargoylesoftware.htmlunit.attachment, + com.gargoylesoftware.htmlunit.gae, + com.gargoylesoftware.htmlunit.html, + com.gargoylesoftware.htmlunit.html.applets, + com.gargoylesoftware.htmlunit.html.impl, + com.gargoylesoftware.htmlunit.html.xpath, + com.gargoylesoftware.htmlunit.javascript, + com.gargoylesoftware.htmlunit.javascript.background, + com.gargoylesoftware.htmlunit.javascript.configuration, + com.gargoylesoftware.htmlunit.javascript.host, + com.gargoylesoftware.htmlunit.javascript.host.arrays, + com.gargoylesoftware.htmlunit.javascript.host.canvas, + com.gargoylesoftware.htmlunit.javascript.host.css, + com.gargoylesoftware.htmlunit.javascript.host.dom, + com.gargoylesoftware.htmlunit.javascript.host.geo, + com.gargoylesoftware.htmlunit.javascript.host.html, + com.gargoylesoftware.htmlunit.javascript.host.svg, + com.gargoylesoftware.htmlunit.javascript.host.xml, + com.gargoylesoftware.htmlunit.javascript.regexp, + com.gargoylesoftware.htmlunit.protocol.about, + com.gargoylesoftware.htmlunit.protocol.data, + com.gargoylesoftware.htmlunit.protocol.javascript, + com.gargoylesoftware.htmlunit.svg, + com.gargoylesoftware.htmlunit.util, + com.gargoylesoftware.htmlunit.xml, + com.google.gwt.core.client, + com.google.gwt.core.client.impl, + com.google.gwt.core.ext, + com.google.gwt.core.ext.debug, + com.google.gwt.core.ext.impl, + com.google.gwt.core.ext.linker, + com.google.gwt.core.ext.linker.impl, + com.google.gwt.core.ext.soyc, + com.google.gwt.core.ext.soyc.coderef, + com.google.gwt.core.ext.soyc.impl, + com.google.gwt.core.ext.typeinfo, + com.google.gwt.core.linker, + com.google.gwt.core.shared, + com.google.gwt.dev, + com.google.gwt.dev.cfg, + com.google.gwt.dev.codeserver, + com.google.gwt.dev.generator, + com.google.gwt.dev.generator.ast, + com.google.gwt.dev.javac, + com.google.gwt.dev.javac.asm, + com.google.gwt.dev.javac.asmbridge, + com.google.gwt.dev.javac.testing, + com.google.gwt.dev.javac.testing.impl, + com.google.gwt.dev.javac.typemodel, + com.google.gwt.dev.jdt, + com.google.gwt.dev.jjs, + com.google.gwt.dev.jjs.ast, + com.google.gwt.dev.jjs.ast.js, + com.google.gwt.dev.jjs.impl, + com.google.gwt.dev.jjs.impl.codesplitter, + com.google.gwt.dev.jjs.impl.gflow, + com.google.gwt.dev.jjs.impl.gflow.cfg, + com.google.gwt.dev.jjs.impl.gflow.constants, + com.google.gwt.dev.jjs.impl.gflow.copy, + com.google.gwt.dev.jjs.impl.gflow.liveness, + com.google.gwt.dev.jjs.impl.gflow.unreachable, + com.google.gwt.dev.js, + com.google.gwt.dev.js.ast, + com.google.gwt.dev.js.rhino, + com.google.gwt.dev.json, + com.google.gwt.dev.protobuf, + com.google.gwt.dev.resource, + com.google.gwt.dev.resource.impl, + com.google.gwt.dev.shell, + com.google.gwt.dev.shell.ie, + com.google.gwt.dev.shell.jetty, + com.google.gwt.dev.shell.log, + com.google.gwt.dev.shell.remoteui, + com.google.gwt.dev.shell.rewrite, + com.google.gwt.dev.ui, + com.google.gwt.dev.url, + com.google.gwt.dev.util, + com.google.gwt.dev.util.arg, + com.google.gwt.dev.util.collect, + com.google.gwt.dev.util.editdistance, + com.google.gwt.dev.util.log, + com.google.gwt.dev.util.log.dashboard, + com.google.gwt.dev.util.log.speedtracer, + com.google.gwt.dev.util.msg, + com.google.gwt.dev.util.transitiveclosure, + com.google.gwt.dev.util.xml, + com.google.gwt.lang, + com.google.gwt.soyc, + com.google.gwt.soyc.io, + com.google.gwt.thirdparty.debugging.sourcemap, + com.google.gwt.thirdparty.debugging.sourcemap.proto, + com.google.gwt.thirdparty.guava.common.annotations, + com.google.gwt.thirdparty.guava.common.base, + com.google.gwt.thirdparty.guava.common.base.internal, + com.google.gwt.thirdparty.guava.common.cache, + com.google.gwt.thirdparty.guava.common.collect, + com.google.gwt.thirdparty.guava.common.escape, + com.google.gwt.thirdparty.guava.common.eventbus, + com.google.gwt.thirdparty.guava.common.hash, + com.google.gwt.thirdparty.guava.common.html, + com.google.gwt.thirdparty.guava.common.io, + com.google.gwt.thirdparty.guava.common.math, + com.google.gwt.thirdparty.guava.common.net, + com.google.gwt.thirdparty.guava.common.primitives, + com.google.gwt.thirdparty.guava.common.reflect, + com.google.gwt.thirdparty.guava.common.util.concurrent, + com.google.gwt.thirdparty.guava.common.xml, + com.google.gwt.thirdparty.javascript.jscomp, + com.google.gwt.thirdparty.javascript.jscomp.ant, + com.google.gwt.thirdparty.javascript.jscomp.deps, + com.google.gwt.thirdparty.javascript.jscomp.fuzzing, + com.google.gwt.thirdparty.javascript.jscomp.graph, + com.google.gwt.thirdparty.javascript.jscomp.parsing, + com.google.gwt.thirdparty.javascript.jscomp.parsing.parser, + com.google.gwt.thirdparty.javascript.jscomp.parsing.parser.codegeneration, + com.google.gwt.thirdparty.javascript.jscomp.parsing.parser.trees, + com.google.gwt.thirdparty.javascript.jscomp.parsing.parser.util, + com.google.gwt.thirdparty.javascript.jscomp.parsing.parser.util.format, + com.google.gwt.thirdparty.javascript.jscomp.regex, + com.google.gwt.thirdparty.javascript.jscomp.type, + com.google.gwt.thirdparty.javascript.rhino, + com.google.gwt.thirdparty.javascript.rhino.head, + com.google.gwt.thirdparty.javascript.rhino.head.annotations, + com.google.gwt.thirdparty.javascript.rhino.head.ast, + com.google.gwt.thirdparty.javascript.rhino.head.commonjs.module, + com.google.gwt.thirdparty.javascript.rhino.head.commonjs.module.provider, + com.google.gwt.thirdparty.javascript.rhino.head.debug, + com.google.gwt.thirdparty.javascript.rhino.head.jdk13, + com.google.gwt.thirdparty.javascript.rhino.head.jdk15, + com.google.gwt.thirdparty.javascript.rhino.head.json, + com.google.gwt.thirdparty.javascript.rhino.head.optimizer, + com.google.gwt.thirdparty.javascript.rhino.head.regexp, + com.google.gwt.thirdparty.javascript.rhino.head.serialize, + com.google.gwt.thirdparty.javascript.rhino.head.tools, + com.google.gwt.thirdparty.javascript.rhino.head.tools.debugger, + com.google.gwt.thirdparty.javascript.rhino.head.tools.debugger.treetable, + com.google.gwt.thirdparty.javascript.rhino.head.tools.idswitch, + com.google.gwt.thirdparty.javascript.rhino.head.tools.jsc, + com.google.gwt.thirdparty.javascript.rhino.head.tools.shell, + com.google.gwt.thirdparty.javascript.rhino.head.v8dtoa, + com.google.gwt.thirdparty.javascript.rhino.head.xml, + com.google.gwt.thirdparty.javascript.rhino.jstype, + com.google.gwt.thirdparty.json, + com.google.gwt.util, + com.google.gwt.util.tools, + com.google.gwt.util.tools.shared, + com.ibm.icu.impl, + com.ibm.icu.impl.data, + com.ibm.icu.impl.duration, + com.ibm.icu.impl.duration.impl, + com.ibm.icu.impl.locale, + com.ibm.icu.lang, + com.ibm.icu.math, + com.ibm.icu.text, + com.ibm.icu.util, + com.steadystate.css.dom, + com.steadystate.css.parser, + com.steadystate.css.parser.selectors, + com.steadystate.css.sac, + com.steadystate.css.userdata, + com.steadystate.css.util, + java_cup, + java_cup.anttask, + java_cup.runtime, + javax.annotation, + javax.annotation.concurrent, + javax.annotation.meta, + javax.servlet, + javax.servlet.annotation, + javax.servlet.descriptor, + javax.servlet.http, + javax.servlet.jsp, + javax.servlet.jsp.el, + javax.servlet.jsp.tagext, + javax.xml, + javax.xml.datatype, + javax.xml.namespace, + javax.xml.parsers, + javax.xml.stream, + javax.xml.stream.events, + javax.xml.stream.util, + javax.xml.transform, + javax.xml.transform.dom, + javax.xml.transform.sax, + javax.xml.transform.stax, + javax.xml.transform.stream, + javax.xml.validation, + javax.xml.xpath, + net.sourceforge.htmlunit.corejs.classfile, + net.sourceforge.htmlunit.corejs.javascript, + net.sourceforge.htmlunit.corejs.javascript.annotations, + net.sourceforge.htmlunit.corejs.javascript.ast, + net.sourceforge.htmlunit.corejs.javascript.commonjs.module, + net.sourceforge.htmlunit.corejs.javascript.commonjs.module.provider, + net.sourceforge.htmlunit.corejs.javascript.debug, + net.sourceforge.htmlunit.corejs.javascript.jdk13, + net.sourceforge.htmlunit.corejs.javascript.jdk15, + net.sourceforge.htmlunit.corejs.javascript.json, + net.sourceforge.htmlunit.corejs.javascript.optimizer, + net.sourceforge.htmlunit.corejs.javascript.regexp, + net.sourceforge.htmlunit.corejs.javascript.serialize, + net.sourceforge.htmlunit.corejs.javascript.tools, + net.sourceforge.htmlunit.corejs.javascript.tools.debugger, + net.sourceforge.htmlunit.corejs.javascript.tools.debugger.treetable, + net.sourceforge.htmlunit.corejs.javascript.tools.shell, + net.sourceforge.htmlunit.corejs.javascript.v8dtoa, + net.sourceforge.htmlunit.corejs.javascript.xml, + netscape.javascript, + org.apache.bcel, + org.apache.bcel.classfile, + org.apache.bcel.generic, + org.apache.bcel.util, + org.apache.bcel.verifier, + org.apache.bcel.verifier.exc, + org.apache.bcel.verifier.statics, + org.apache.bcel.verifier.structurals, + org.apache.commons.codec, + org.apache.commons.codec.binary, + org.apache.commons.codec.digest, + org.apache.commons.codec.language, + org.apache.commons.codec.language.bm, + org.apache.commons.codec.net, + org.apache.commons.collections, + org.apache.commons.collections.bag, + org.apache.commons.collections.bidimap, + org.apache.commons.collections.buffer, + org.apache.commons.collections.collection, + org.apache.commons.collections.comparators, + org.apache.commons.collections.functors, + org.apache.commons.collections.iterators, + org.apache.commons.collections.keyvalue, + org.apache.commons.collections.list, + org.apache.commons.collections.map, + org.apache.commons.collections.set, + org.apache.commons.el, + org.apache.commons.el.parser, + org.apache.commons.io, + org.apache.commons.io.comparator, + org.apache.commons.io.filefilter, + org.apache.commons.io.input, + org.apache.commons.io.monitor, + org.apache.commons.io.output, + org.apache.commons.lang3, + org.apache.commons.lang3.builder, + org.apache.commons.lang3.concurrent, + org.apache.commons.lang3.event, + org.apache.commons.lang3.exception, + org.apache.commons.lang3.math, + org.apache.commons.lang3.mutable, + org.apache.commons.lang3.reflect, + org.apache.commons.lang3.text, + org.apache.commons.lang3.text.translate, + org.apache.commons.lang3.time, + org.apache.commons.lang3.tuple, + org.apache.commons.logging, + org.apache.commons.logging.impl, + org.apache.html.dom, + org.apache.http, + org.apache.http.annotation, + org.apache.http.auth, + org.apache.http.auth.params, + org.apache.http.client, + org.apache.http.client.config, + org.apache.http.client.entity, + org.apache.http.client.methods, + org.apache.http.client.params, + org.apache.http.client.protocol, + org.apache.http.client.utils, + org.apache.http.concurrent, + org.apache.http.config, + org.apache.http.conn, + org.apache.http.conn.params, + org.apache.http.conn.routing, + org.apache.http.conn.scheme, + org.apache.http.conn.socket, + org.apache.http.conn.ssl, + org.apache.http.conn.util, + org.apache.http.cookie, + org.apache.http.cookie.params, + org.apache.http.entity, + org.apache.http.entity.mime, + org.apache.http.entity.mime.content, + org.apache.http.impl, + org.apache.http.impl.auth, + org.apache.http.impl.client, + org.apache.http.impl.conn, + org.apache.http.impl.conn.tsccm, + org.apache.http.impl.cookie, + org.apache.http.impl.entity, + org.apache.http.impl.execchain, + org.apache.http.impl.io, + org.apache.http.impl.pool, + org.apache.http.io, + org.apache.http.message, + org.apache.http.params, + org.apache.http.pool, + org.apache.http.protocol, + org.apache.http.util, + org.apache.james.mime4j, + org.apache.james.mime4j.codec, + org.apache.james.mime4j.descriptor, + org.apache.james.mime4j.field, + org.apache.james.mime4j.field.address, + org.apache.james.mime4j.field.address.parser, + org.apache.james.mime4j.field.contentdisposition.parser, + org.apache.james.mime4j.field.contenttype.parser, + org.apache.james.mime4j.field.datetime, + org.apache.james.mime4j.field.datetime.parser, + org.apache.james.mime4j.field.language.parser, + org.apache.james.mime4j.field.mimeversion.parser, + org.apache.james.mime4j.field.structured.parser, + org.apache.james.mime4j.io, + org.apache.james.mime4j.message, + org.apache.james.mime4j.parser, + org.apache.james.mime4j.storage, + org.apache.james.mime4j.util, + org.apache.jasper, + org.apache.jasper.compiler, + org.apache.jasper.compiler.tagplugin, + org.apache.jasper.runtime, + org.apache.jasper.security, + org.apache.jasper.servlet, + org.apache.jasper.tagplugins.jstl, + org.apache.jasper.util, + org.apache.jasper.xmlparser, + org.apache.regexp, + org.apache.tapestry.util.text, + org.apache.tools.ant, + org.apache.tools.ant.filters, + org.apache.tools.ant.filters.util, + org.apache.tools.ant.helper, + org.apache.tools.ant.input, + org.apache.tools.ant.listener, + org.apache.tools.ant.loader, + org.apache.tools.ant.taskdefs, + org.apache.tools.ant.taskdefs.compilers, + org.apache.tools.ant.taskdefs.condition, + org.apache.tools.ant.taskdefs.cvslib, + org.apache.tools.ant.taskdefs.email, + org.apache.tools.ant.taskdefs.rmic, + org.apache.tools.ant.types, + org.apache.tools.ant.types.mappers, + org.apache.tools.ant.types.resolver, + org.apache.tools.ant.types.selectors, + org.apache.tools.ant.types.selectors.modifiedselector, + org.apache.tools.ant.util, + org.apache.tools.ant.util.facade, + org.apache.tools.ant.util.regexp, + org.apache.tools.bzip2, + org.apache.tools.mail, + org.apache.tools.tar, + org.apache.tools.zip, + org.apache.wml, + org.apache.wml.dom, + org.apache.xalan, + org.apache.xalan.client, + org.apache.xalan.extensions, + org.apache.xalan.lib, + org.apache.xalan.lib.sql, + org.apache.xalan.processor, + org.apache.xalan.res, + org.apache.xalan.serialize, + org.apache.xalan.templates, + org.apache.xalan.trace, + org.apache.xalan.transformer, + org.apache.xalan.xslt, + org.apache.xalan.xsltc, + org.apache.xalan.xsltc.cmdline, + org.apache.xalan.xsltc.cmdline.getopt, + org.apache.xalan.xsltc.compiler, + org.apache.xalan.xsltc.compiler.util, + org.apache.xalan.xsltc.dom, + org.apache.xalan.xsltc.runtime, + org.apache.xalan.xsltc.runtime.output, + org.apache.xalan.xsltc.trax, + org.apache.xalan.xsltc.util, + org.apache.xerces.dom, + org.apache.xerces.dom.events, + org.apache.xerces.dom3.as, + org.apache.xerces.impl, + org.apache.xerces.impl.dtd, + org.apache.xerces.impl.dtd.models, + org.apache.xerces.impl.dv, + org.apache.xerces.impl.dv.dtd, + org.apache.xerces.impl.dv.util, + org.apache.xerces.impl.dv.xs, + org.apache.xerces.impl.io, + org.apache.xerces.impl.msg, + org.apache.xerces.impl.validation, + org.apache.xerces.impl.xpath, + org.apache.xerces.impl.xpath.regex, + org.apache.xerces.impl.xs, + org.apache.xerces.impl.xs.identity, + org.apache.xerces.impl.xs.models, + org.apache.xerces.impl.xs.opti, + org.apache.xerces.impl.xs.traversers, + org.apache.xerces.impl.xs.util, + org.apache.xerces.jaxp, + org.apache.xerces.jaxp.datatype, + org.apache.xerces.jaxp.validation, + org.apache.xerces.parsers, + org.apache.xerces.stax, + org.apache.xerces.stax.events, + org.apache.xerces.util, + org.apache.xerces.xinclude, + org.apache.xerces.xni, + org.apache.xerces.xni.grammars, + org.apache.xerces.xni.parser, + org.apache.xerces.xpointer, + org.apache.xerces.xs, + org.apache.xerces.xs.datatypes, + org.apache.xml.dtm, + org.apache.xml.dtm.ref, + org.apache.xml.dtm.ref.dom2dtm, + org.apache.xml.dtm.ref.sax2dtm, + org.apache.xml.res, + org.apache.xml.serialize, + org.apache.xml.serializer, + org.apache.xml.serializer.dom3, + org.apache.xml.serializer.utils, + org.apache.xml.utils, + org.apache.xml.utils.res, + org.apache.xmlcommons, + org.apache.xpath, + org.apache.xpath.axes, + org.apache.xpath.compiler, + org.apache.xpath.domapi, + org.apache.xpath.functions, + org.apache.xpath.jaxp, + org.apache.xpath.objects, + org.apache.xpath.operations, + org.apache.xpath.patterns, + org.apache.xpath.res, + org.cyberneko.html, + org.cyberneko.html.filters, + org.cyberneko.html.parsers, + org.cyberneko.html.xercesbridge, + org.eclipse.jdt.core, + org.eclipse.jdt.core.compiler, + org.eclipse.jdt.core.compiler.batch, + org.eclipse.jdt.internal.antadapter, + org.eclipse.jdt.internal.compiler, + org.eclipse.jdt.internal.compiler.ast, + org.eclipse.jdt.internal.compiler.batch, + org.eclipse.jdt.internal.compiler.classfmt, + org.eclipse.jdt.internal.compiler.codegen, + org.eclipse.jdt.internal.compiler.env, + org.eclipse.jdt.internal.compiler.flow, + org.eclipse.jdt.internal.compiler.impl, + org.eclipse.jdt.internal.compiler.lookup, + org.eclipse.jdt.internal.compiler.parser, + org.eclipse.jdt.internal.compiler.parser.diagnose, + org.eclipse.jdt.internal.compiler.problem, + org.eclipse.jdt.internal.compiler.util, + org.eclipse.jdt.internal.core.util, + org.eclipse.jetty.continuation, + org.eclipse.jetty.http, + org.eclipse.jetty.http.gzip, + org.eclipse.jetty.http.ssl, + org.eclipse.jetty.io, + org.eclipse.jetty.io.bio, + org.eclipse.jetty.io.nio, + org.eclipse.jetty.security, + org.eclipse.jetty.security.authentication, + org.eclipse.jetty.server, + org.eclipse.jetty.server.bio, + org.eclipse.jetty.server.handler, + org.eclipse.jetty.server.handler.jmx, + org.eclipse.jetty.server.jmx, + org.eclipse.jetty.server.nio, + org.eclipse.jetty.server.session, + org.eclipse.jetty.server.session.jmx, + org.eclipse.jetty.server.ssl, + org.eclipse.jetty.servlet, + org.eclipse.jetty.servlet.jmx, + org.eclipse.jetty.servlet.listener, + org.eclipse.jetty.servlets, + org.eclipse.jetty.util, + org.eclipse.jetty.util.ajax, + org.eclipse.jetty.util.component, + org.eclipse.jetty.util.log, + org.eclipse.jetty.util.preventers, + org.eclipse.jetty.util.resource, + org.eclipse.jetty.util.security, + org.eclipse.jetty.util.ssl, + org.eclipse.jetty.util.statistic, + org.eclipse.jetty.util.thread, + org.eclipse.jetty.webapp, + org.eclipse.jetty.websocket, + org.eclipse.jetty.xml, + org.objectweb.asm, + org.objectweb.asm.commons, + org.objectweb.asm.signature, + org.objectweb.asm.tree, + org.objectweb.asm.tree.analysis, + org.objectweb.asm.util, + org.objectweb.asm.xml, + org.w3c.css.sac, + org.w3c.css.sac.helpers, + org.w3c.dom, + org.w3c.dom.bootstrap, + org.w3c.dom.css, + org.w3c.dom.events, + org.w3c.dom.html, + org.w3c.dom.ls, + org.w3c.dom.ranges, + org.w3c.dom.stylesheets, + org.w3c.dom.traversal, + org.w3c.dom.views, + org.w3c.dom.xpath, + org.xml.sax, + org.xml.sax.ext, + org.xml.sax.helpers diff --git a/java/com.google.gwt.dev/build.properties b/java/com.google.gwt.dev/build.properties new file mode 100755 index 00000000000..2b37c8da50e --- /dev/null +++ b/java/com.google.gwt.dev/build.properties @@ -0,0 +1,3 @@ +bin.includes = META-INF/,\ + .,\ + lib/gwt-dev.jar diff --git a/java/com.google.gwt.osgi/lib/gwt-dev.jar b/java/com.google.gwt.dev/lib/gwt-dev.jar similarity index 100% rename from java/com.google.gwt.osgi/lib/gwt-dev.jar rename to java/com.google.gwt.dev/lib/gwt-dev.jar diff --git a/java/com.google.gwt.dev/pom.xml b/java/com.google.gwt.dev/pom.xml new file mode 100755 index 00000000000..845aa49059e --- /dev/null +++ b/java/com.google.gwt.dev/pom.xml @@ -0,0 +1,13 @@ + + + 4.0.0 + + root + com.sap.sailing + 1.0.0-SNAPSHOT + + com.google.gwt.dev + 2.7.0 + eclipse-plugin + diff --git a/java/com.google.gwt.osgi/META-INF/MANIFEST.MF b/java/com.google.gwt.osgi/META-INF/MANIFEST.MF old mode 100644 new mode 100755 index 8c41d90b6e2..8827322a846 --- a/java/com.google.gwt.osgi/META-INF/MANIFEST.MF +++ b/java/com.google.gwt.osgi/META-INF/MANIFEST.MF @@ -172,6 +172,22 @@ Export-Package: com.google.gwt.activity.shared, com.google.web.bindery.requestfactory.vm.testing, org.hibernate.validator, org.hibernate.validator.engine -Require-Bundle: javax.validation;bundle-version="1.0.0" -Import-Package: javax.servlet;version="2.6.0", - javax.servlet.http;version="2.6.0" +Require-Bundle: javax.validation;bundle-version="1.0.0";resolution:=optional, + org.junit4;bundle-version="4.8.2";resolution:=optional +Import-Package: com.gargoylesoftware.htmlunit;resolution:=optional, + com.google.gwt.core.ext;resolution:=optional, + com.google.gwt.core.ext.typeinfo;resolution:=optional, + com.google.gwt.dev;resolution:=optional, + com.google.gwt.dev.cfg;resolution:=optional, + com.google.gwt.dev.javac.typemodel;resolution:=optional, + com.google.gwt.dev.shell;resolution:=optional, + com.google.gwt.dev.shell.jetty;resolution:=optional, + com.google.gwt.dev.util.arg;resolution:=optional, + com.google.gwt.dev.util.collect;resolution:=optional, + com.google.gwt.junit;resolution:=optional, + com.google.gwt.thirdparty.guava.common.collect;resolution:=optional, + com.google.gwt.util.tools;resolution:=optional, + javax.servlet;version="2.6.0";resolution:=optional, + javax.servlet.http;version="2.6.0";resolution:=optional, + org.eclipse.jetty.server;resolution:=optional, + org.eclipse.jetty.webapp;resolution:=optional diff --git a/java/com.sap.sailing.dashboards.gwt/META-INF/MANIFEST.MF b/java/com.sap.sailing.dashboards.gwt/META-INF/MANIFEST.MF index 3e9aea38dba..4366e50b793 100644 --- a/java/com.sap.sailing.dashboards.gwt/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.dashboards.gwt/META-INF/MANIFEST.MF @@ -21,7 +21,8 @@ Require-Bundle: com.sap.sailing.domain, com.sap.sailing.declination, org.json.simple;bundle-version="1.1.0", com.sap.sailing.udpconnector, - com.googlecode.mgwt;bundle-version="2.0.0" + com.googlecode.mgwt;bundle-version="2.0.0", + com.sap.sse.gwt.adminconsole Bundle-ActivationPolicy: lazy Import-Package: org.osgi.framework;version="1.5.0", org.osgi.util.tracker diff --git a/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT sdm.launch b/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT sdm.launch index b0e93eef659..694e0ceb278 100644 --- a/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT sdm.launch +++ b/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT sdm.launch @@ -1,40 +1,41 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/resources/images/compass.png b/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/compass.png similarity index 100% rename from java/com.sap.sailing.dashboards.gwt/src/main/resources/images/compass.png rename to java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/compass.png diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/resources/images/dashboardicon.png b/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/dashboardicon.png similarity index 100% rename from java/com.sap.sailing.dashboards.gwt/src/main/resources/images/dashboardicon.png rename to java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/dashboardicon.png diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/resources/images/hourwatch.png b/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/hourwatch.png similarity index 100% rename from java/com.sap.sailing.dashboards.gwt/src/main/resources/images/hourwatch.png rename to java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/hourwatch.png diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/resources/images/leftarrow.png b/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/leftarrow.png similarity index 100% rename from java/com.sap.sailing.dashboards.gwt/src/main/resources/images/leftarrow.png rename to java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/leftarrow.png diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/resources/images/leftarrow_disabled.png b/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/leftarrow_disabled.png similarity index 100% rename from java/com.sap.sailing.dashboards.gwt/src/main/resources/images/leftarrow_disabled.png rename to java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/leftarrow_disabled.png diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/resources/images/logo_sap.png b/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/logo_sap.png similarity index 100% rename from java/com.sap.sailing.dashboards.gwt/src/main/resources/images/logo_sap.png rename to java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/logo_sap.png diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/resources/images/logoess.png b/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/logoess.png similarity index 100% rename from java/com.sap.sailing.dashboards.gwt/src/main/resources/images/logoess.png rename to java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/logoess.png diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/resources/images/nointernet.png b/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/nointernet.png similarity index 100% rename from java/com.sap.sailing.dashboards.gwt/src/main/resources/images/nointernet.png rename to java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/nointernet.png diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/resources/images/rightarrow.png b/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/rightarrow.png similarity index 100% rename from java/com.sap.sailing.dashboards.gwt/src/main/resources/images/rightarrow.png rename to java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/rightarrow.png diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/resources/images/rightarrow_disabled.png b/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/rightarrow_disabled.png similarity index 100% rename from java/com.sap.sailing.dashboards.gwt/src/main/resources/images/rightarrow_disabled.png rename to java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/rightarrow_disabled.png diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/resources/images/settings.png b/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/settings.png similarity index 100% rename from java/com.sap.sailing.dashboards.gwt/src/main/resources/images/settings.png rename to java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/settings.png diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/resources/images/watch.png b/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/watch.png similarity index 100% rename from java/com.sap.sailing.dashboards.gwt/src/main/resources/images/watch.png rename to java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/watch.png diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/resources/images/windarrow.png b/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/windarrow.png similarity index 100% rename from java/com.sap.sailing.dashboards.gwt/src/main/resources/images/windarrow.png rename to java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/windarrow.png diff --git a/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/functions/TestSailingFunctionsRegistration.java b/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/functions/TestSailingFunctionsRegistration.java index ec2d199933b..34bb6f7f1ca 100644 --- a/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/functions/TestSailingFunctionsRegistration.java +++ b/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/functions/TestSailingFunctionsRegistration.java @@ -15,6 +15,7 @@ import com.sap.sailing.datamining.Activator; import com.sap.sailing.datamining.data.HasGPSFixContext; import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; import com.sap.sailing.datamining.data.HasTrackedRaceContext; +import com.sap.sailing.datamining.test.util.OpenFunctionManager; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Moving; import com.sap.sailing.domain.base.Nationality; @@ -23,18 +24,16 @@ import com.sap.sailing.domain.common.Speed; import com.sap.sse.common.Named; import com.sap.sse.datamining.factories.FunctionFactory; import com.sap.sse.datamining.functions.Function; -import com.sap.sse.datamining.functions.FunctionRegistry; -import com.sap.sse.datamining.impl.functions.SimpleFunctionRegistry; public class TestSailingFunctionsRegistration { private static FunctionFactory functionFactory = new FunctionFactory(); - private FunctionRegistry functionRegistry; + private OpenFunctionManager functionRegistry; @Before public void setUpFunctionRegistryAndProvider() { - functionRegistry = new SimpleFunctionRegistry(); + functionRegistry = new OpenFunctionManager(); functionRegistry.registerAllWithInternalFunctionPolicy(Activator.getDefault().getInternalClassesWithMarkedMethods()); } @@ -76,7 +75,7 @@ public class TestSailingFunctionsRegistration { @Test public void testImportantRegisteredStatistics() throws NoSuchMethodException, SecurityException { Collection> expectedStatistics = createExpectedImportantRegisteredStatistics(); - Collection> providedStatistics = functionRegistry.getStatistics(); + Collection> providedStatistics = functionRegistry.getAllStatistics(); for (Function statistic : expectedStatistics) { assertThat("The expected statistic '" + statistic + "' wasn't provided.", providedStatistics.contains(statistic), is(true)); diff --git a/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/test/util/OpenFunctionManager.java b/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/test/util/OpenFunctionManager.java new file mode 100644 index 00000000000..137c69bfb3c --- /dev/null +++ b/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/test/util/OpenFunctionManager.java @@ -0,0 +1,18 @@ +package com.sap.sailing.datamining.test.util; + +import java.util.Collection; + +import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.impl.functions.FunctionManager; + +public class OpenFunctionManager extends FunctionManager { + + public Collection> getDimensions() { + return asSet(dimensions); + } + + public Collection> getExternalFunctions() { + return asSet(externalFunctions); + } + +} diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties index dd8b19be160..370f6b81434 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties @@ -3,7 +3,8 @@ DistanceTraveled=zur Boat=Boot Team=Team Name=Name -NationalityAcronym=Nationalität +Nationality=Nationalitäts +Acronym=Abkürzung SailID=Segelnummer LegNumber=Nummer des Schenkels LegType=Art des Schenkels @@ -13,12 +14,30 @@ CourseArea=Regattabahn BoatClass=Bootsklasse Fleet=Flotte Race=Rennen -SailingDomainRetrieverChain=Segel-Domänen Datenbeschaffer +LegSailingDomainRetrieverChain=Segel-Domänen Leg-Beschaffer +GPSFixSailingDomainRetrieverChain=Segel-Domänen GPS-Fix-Beschaffer +MarkPassingSailingDomainRetrieverChain=Segel-Domänen Tonnenrundungs-Beschaffer LeaderboardGroup=Ranglisten Gruppe RegattaLeaderboard=Regatta Rangliste Leg=Schenkel +MarkPassing=Tonnenrundung LegOfCompetitor=Schenkel eines Teilnehmers GpsFix=GPS-Fix Speed=Geschwindigkeit +SpeedTenSecondsBefore=Geschwindigkeit 10s vorher InKnots=in Knoten -WindStrength=Windstärke \ No newline at end of file +WindStrengthInBeaufort=Windstärke in Beaufort +BftClusterGroup=Windstärke in Beaufort +Bft0=Windstille +Bft1=leiser Zug +Bft2=leichte Brise +Bft3=schwacher Wind +Bft4=mäßiger Wind +Bft5=frischer Wind +Bft6=starker Wind +Bft7=steifer Wind +Bft8=stürmischer Wind +Bft9=Sturm +Bft10=schwerer Sturm +Bft11=orkanartiger Sturm +Bft12=Orkan \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_en.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_en.properties index 62e92c3db80..034d610a9aa 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_en.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_en.properties @@ -1,4 +1,5 @@ Speed=Speed +SpeedTenSecondsBefore=Speed 10s before InKnots=in knots DistanceTraveled=Distance Traveled Name=Name @@ -6,7 +7,8 @@ LegType=Leg Type SailID=Sail Number Year=Year LegNumber=Leg Number -NationalityAcronym=Nationality +Nationality=Nationality +Acronym=Acronym Regatta=Regatta CourseArea=Course Area BoatClass=Boat Class @@ -15,10 +17,27 @@ Race=Race Competitor=Competitor Boat=Boat Team=Team -SailingDomainRetrieverChain=Sailing Domain Retriever +LegSailingDomainRetrieverChain=Sailing Domain Leg Retriever +GPSFixSailingDomainRetrieverChain=Sailing Domain GPS Fix Retriever +MarkPassingSailingDomainRetrieverChain=Sailing Domain Mark Passing Retriever LeaderboardGroup=Leaderboard Group RegattaLeaderboard=Regatta Leaderboard Leg=Leg +MarkPassing=Mark Passing LegOfCompetitor=Leg of competitor GpsFix=GPS-Fix -WindStrength=Wind Strength \ No newline at end of file +WindStrengthInBeaufort=Wind Strength in Beaufort +BftClusterGroup=Wind Strength in Beaufort +Bft0=Calm +Bft1=Light air +Bft2=Light breeze +Bft3=Gentle breeze +Bft4=Moderate breeze +Bft5=Fresh breeze +Bft6=Strong breeze +Bft7=High wind +Bft8=Gale +Bft9=Strong gale +Bft10=Storm +Bft11=Violent storm +Bft12=Hurricane \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/Activator.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/Activator.java index 08c95073587..f1acd00bf23 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/Activator.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/Activator.java @@ -9,6 +9,7 @@ import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import com.sap.sailing.datamining.data.HasGPSFixContext; +import com.sap.sailing.datamining.data.HasMarkPassingContext; import com.sap.sailing.datamining.data.HasTrackedLegContext; import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; import com.sap.sailing.datamining.data.HasTrackedRaceContext; @@ -31,7 +32,7 @@ public class Activator implements BundleActivator, DataMiningBundleService { public Activator() { dataRetrieverChainDefinitions = new SailingDataRetrieverChainDefinitions(); - sailingServerStringMessages = new ResourceBundleStringMessagesImpl(STRING_MESSAGES_BASE_NAME, Activator.class.getClassLoader()); + sailingServerStringMessages = new ResourceBundleStringMessagesImpl(STRING_MESSAGES_BASE_NAME, getClass().getClassLoader()); clusterGroups = new SailingClusterGroups(); } @@ -59,6 +60,7 @@ public class Activator implements BundleActivator, DataMiningBundleService { internalClasses.add(HasTrackedLegContext.class); internalClasses.add(HasTrackedLegOfCompetitorContext.class); internalClasses.add(HasGPSFixContext.class); + internalClasses.add(HasMarkPassingContext.class); return internalClasses; } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingClusterGroups.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingClusterGroups.java index ee2336925f8..4330ba31c47 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingClusterGroups.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingClusterGroups.java @@ -28,79 +28,79 @@ public class SailingClusterGroups { ClusterBoundary lowerBound = new ComparatorClusterBoundary(comparableComparator, lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN); Speed upperBoundWindSpeed = new KnotSpeedImpl(1.0); ClusterBoundary upperBound = new ComparatorClusterBoundary(comparableComparator, upperBoundWindSpeed, ComparisonStrategy.LOWER_THAN); - clusters.add(new ClusterWithLowerAndUpperBoundaries("Calm", lowerBound, upperBound)); + clusters.add(new ClusterWithLowerAndUpperBoundaries("Bft0", lowerBound, upperBound)); lowerBoundWindSpeed = new KnotSpeedImpl(1.0); lowerBound = new ComparatorClusterBoundary(comparableComparator, lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN); upperBoundWindSpeed = new KnotSpeedImpl(4.0); upperBound = new ComparatorClusterBoundary(comparableComparator, upperBoundWindSpeed, ComparisonStrategy.LOWER_THAN); - clusters.add(new ClusterWithLowerAndUpperBoundaries("Light air", lowerBound, upperBound)); + clusters.add(new ClusterWithLowerAndUpperBoundaries("Bft1", lowerBound, upperBound)); lowerBoundWindSpeed = new KnotSpeedImpl(4.0); lowerBound = new ComparatorClusterBoundary(comparableComparator, lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN); upperBoundWindSpeed = new KnotSpeedImpl(7.0); upperBound = new ComparatorClusterBoundary(comparableComparator, upperBoundWindSpeed, ComparisonStrategy.LOWER_THAN); - clusters.add(new ClusterWithLowerAndUpperBoundaries("Light breeze", lowerBound, upperBound)); + clusters.add(new ClusterWithLowerAndUpperBoundaries("Bft2", lowerBound, upperBound)); lowerBoundWindSpeed = new KnotSpeedImpl(7.0); lowerBound = new ComparatorClusterBoundary(comparableComparator, lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN); upperBoundWindSpeed = new KnotSpeedImpl(11.0); upperBound = new ComparatorClusterBoundary(comparableComparator, upperBoundWindSpeed, ComparisonStrategy.LOWER_THAN); - clusters.add(new ClusterWithLowerAndUpperBoundaries("Gentle breeze", lowerBound, upperBound)); + clusters.add(new ClusterWithLowerAndUpperBoundaries("Bft3", lowerBound, upperBound)); lowerBoundWindSpeed = new KnotSpeedImpl(11.0); lowerBound = new ComparatorClusterBoundary(comparableComparator, lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN); upperBoundWindSpeed = new KnotSpeedImpl(16.0); upperBound = new ComparatorClusterBoundary(comparableComparator, upperBoundWindSpeed, ComparisonStrategy.LOWER_THAN); - clusters.add(new ClusterWithLowerAndUpperBoundaries("Moderate breeze", lowerBound, upperBound)); + clusters.add(new ClusterWithLowerAndUpperBoundaries("Bft4", lowerBound, upperBound)); lowerBoundWindSpeed = new KnotSpeedImpl(16.0); lowerBound = new ComparatorClusterBoundary(comparableComparator, lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN); upperBoundWindSpeed = new KnotSpeedImpl(22.0); upperBound = new ComparatorClusterBoundary(comparableComparator, upperBoundWindSpeed, ComparisonStrategy.LOWER_THAN); - clusters.add(new ClusterWithLowerAndUpperBoundaries("Fresh breeze", lowerBound, upperBound)); + clusters.add(new ClusterWithLowerAndUpperBoundaries("Bft5", lowerBound, upperBound)); lowerBoundWindSpeed = new KnotSpeedImpl(22.0); lowerBound = new ComparatorClusterBoundary(comparableComparator, lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN); upperBoundWindSpeed = new KnotSpeedImpl(28.0); upperBound = new ComparatorClusterBoundary(comparableComparator, upperBoundWindSpeed, ComparisonStrategy.LOWER_THAN); - clusters.add(new ClusterWithLowerAndUpperBoundaries("Strong breeze", lowerBound, upperBound)); + clusters.add(new ClusterWithLowerAndUpperBoundaries("Bft6", lowerBound, upperBound)); lowerBoundWindSpeed = new KnotSpeedImpl(28.0); lowerBound = new ComparatorClusterBoundary(comparableComparator, lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN); upperBoundWindSpeed = new KnotSpeedImpl(34.0); upperBound = new ComparatorClusterBoundary(comparableComparator, upperBoundWindSpeed, ComparisonStrategy.LOWER_THAN); - clusters.add(new ClusterWithLowerAndUpperBoundaries("High wind", lowerBound, upperBound)); + clusters.add(new ClusterWithLowerAndUpperBoundaries("Bft7", lowerBound, upperBound)); lowerBoundWindSpeed = new KnotSpeedImpl(34.0); lowerBound = new ComparatorClusterBoundary(comparableComparator, lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN); upperBoundWindSpeed = new KnotSpeedImpl(41.0); upperBound = new ComparatorClusterBoundary(comparableComparator, upperBoundWindSpeed, ComparisonStrategy.LOWER_THAN); - clusters.add(new ClusterWithLowerAndUpperBoundaries("Gale", lowerBound, upperBound)); + clusters.add(new ClusterWithLowerAndUpperBoundaries("Bft8", lowerBound, upperBound)); lowerBoundWindSpeed = new KnotSpeedImpl(41.0); lowerBound = new ComparatorClusterBoundary(comparableComparator, lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN); upperBoundWindSpeed = new KnotSpeedImpl(48.0); upperBound = new ComparatorClusterBoundary(comparableComparator, upperBoundWindSpeed, ComparisonStrategy.LOWER_THAN); - clusters.add(new ClusterWithLowerAndUpperBoundaries("Strong gale", lowerBound, upperBound)); + clusters.add(new ClusterWithLowerAndUpperBoundaries("Bft9", lowerBound, upperBound)); lowerBoundWindSpeed = new KnotSpeedImpl(48.0); lowerBound = new ComparatorClusterBoundary(comparableComparator, lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN); upperBoundWindSpeed = new KnotSpeedImpl(56.0); upperBound = new ComparatorClusterBoundary(comparableComparator, upperBoundWindSpeed, ComparisonStrategy.LOWER_THAN); - clusters.add(new ClusterWithLowerAndUpperBoundaries("Storm", lowerBound, upperBound)); + clusters.add(new ClusterWithLowerAndUpperBoundaries("Bft10", lowerBound, upperBound)); lowerBoundWindSpeed = new KnotSpeedImpl(56.0); lowerBound = new ComparatorClusterBoundary(comparableComparator, lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN); upperBoundWindSpeed = new KnotSpeedImpl(64.0); upperBound = new ComparatorClusterBoundary(comparableComparator, upperBoundWindSpeed, ComparisonStrategy.LOWER_THAN); - clusters.add(new ClusterWithLowerAndUpperBoundaries("Violent Storm", lowerBound, upperBound)); + clusters.add(new ClusterWithLowerAndUpperBoundaries("Bft11", lowerBound, upperBound)); lowerBoundWindSpeed = new KnotSpeedImpl(64.0); lowerBound = new ComparatorClusterBoundary(comparableComparator, lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN); - clusters.add(new ClusterWithSingleBoundary("Hurricane", lowerBound)); + clusters.add(new ClusterWithSingleBoundary("Bft12", lowerBound)); - windStrengthInBeaufortCluster = new FixClusterGroup("Wind Strength", clusters); + windStrengthInBeaufortCluster = new FixClusterGroup("BftClusterGroup", clusters); } public ClusterGroup getWindStrengthInBeaufortCluster() { diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingDataRetrieverChainDefinitions.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingDataRetrieverChainDefinitions.java index 0b0d3cb35ce..a1a6cc23517 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingDataRetrieverChainDefinitions.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingDataRetrieverChainDefinitions.java @@ -4,20 +4,21 @@ import java.util.ArrayList; import java.util.Collection; import com.sap.sailing.datamining.data.HasGPSFixContext; +import com.sap.sailing.datamining.data.HasMarkPassingContext; import com.sap.sailing.datamining.data.HasTrackedLegContext; import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; import com.sap.sailing.datamining.data.HasTrackedRaceContext; import com.sap.sailing.datamining.impl.components.GPSFixRetrievalProcessor; import com.sap.sailing.datamining.impl.components.LeaderboardGroupRetrievalProcessor; -import com.sap.sailing.datamining.impl.components.RegattaLeaderboardFilteringRetrievalProcessor; -import com.sap.sailing.datamining.impl.components.TrackedLegFilteringRetrievalProcessor; -import com.sap.sailing.datamining.impl.components.TrackedLegOfCompetitorFilteringRetrievalProcessor; -import com.sap.sailing.datamining.impl.components.TrackedRaceFilteringRetrievalProcessor; +import com.sap.sailing.datamining.impl.components.MarkPassingRetrievalProcessor; +import com.sap.sailing.datamining.impl.components.RegattaLeaderboardRetrievalProcessor; +import com.sap.sailing.datamining.impl.components.TrackedLegOfCompetitorRetrievalProcessor; +import com.sap.sailing.datamining.impl.components.TrackedLegRetrievalProcessor; +import com.sap.sailing.datamining.impl.components.TrackedRaceRetrievalProcessor; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; import com.sap.sailing.server.RacingEventService; import com.sap.sse.datamining.DataRetrieverChainDefinition; -import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.impl.SimpleDataRetrieverChainDefinition; public class SailingDataRetrieverChainDefinitions { @@ -27,33 +28,29 @@ public class SailingDataRetrieverChainDefinitions { public SailingDataRetrieverChainDefinitions() { dataRetrieverChainDefinitions = new ArrayList<>(); - DataRetrieverChainDefinition legOfCompetitorRetrieverChainDefinition = new SimpleDataRetrieverChainDefinition<>( - RacingEventService.class, HasTrackedLegOfCompetitorContext.class, "SailingDomainRetrieverChain"); - @SuppressWarnings("unchecked") - Class> leaderboardGroupRetrieverType = (Class>) (Class) LeaderboardGroupRetrievalProcessor.class; - legOfCompetitorRetrieverChainDefinition.startWith(leaderboardGroupRetrieverType, LeaderboardGroup.class, "LeaderboardGroup"); - @SuppressWarnings("unchecked") - Class> regattaLeaderboardRetrieverType = (Class>) (Class) RegattaLeaderboardFilteringRetrievalProcessor.class; - legOfCompetitorRetrieverChainDefinition.addAfter(leaderboardGroupRetrieverType, regattaLeaderboardRetrieverType, + final DataRetrieverChainDefinition trackedRaceRetrieverChainDefinition = new SimpleDataRetrieverChainDefinition<>( + RacingEventService.class, HasTrackedLegOfCompetitorContext.class, "RaceSailingDomainRetrieverChain"); + trackedRaceRetrieverChainDefinition.startWith(LeaderboardGroupRetrievalProcessor.class, LeaderboardGroup.class, "LeaderboardGroup"); + trackedRaceRetrieverChainDefinition.addAfter(LeaderboardGroupRetrievalProcessor.class, RegattaLeaderboardRetrievalProcessor.class, RegattaLeaderboard.class, "RegattaLeaderboard"); - @SuppressWarnings("unchecked") - Class> raceRetrieverType = (Class>) (Class) TrackedRaceFilteringRetrievalProcessor.class; - legOfCompetitorRetrieverChainDefinition.addAfter(regattaLeaderboardRetrieverType, raceRetrieverType, + trackedRaceRetrieverChainDefinition.addAfter(RegattaLeaderboardRetrievalProcessor.class, TrackedRaceRetrievalProcessor.class, HasTrackedRaceContext.class, "Race"); - @SuppressWarnings("unchecked") - Class> legRetrieverType = (Class>) (Class) TrackedLegFilteringRetrievalProcessor.class; - legOfCompetitorRetrieverChainDefinition.addAfter(raceRetrieverType, legRetrieverType, HasTrackedLegContext.class, "Leg"); - @SuppressWarnings("unchecked") - Class> legOfCompetitorRetrieverType = (Class>) (Class) TrackedLegOfCompetitorFilteringRetrievalProcessor.class; - legOfCompetitorRetrieverChainDefinition.endWith(legRetrieverType, legOfCompetitorRetrieverType, + + final DataRetrieverChainDefinition markPassingRetrieverChainDefinition = new SimpleDataRetrieverChainDefinition<>( + trackedRaceRetrieverChainDefinition, HasMarkPassingContext.class, "MarkPassingSailingDomainRetrieverChain"); + markPassingRetrieverChainDefinition.endWith(TrackedRaceRetrievalProcessor.class, MarkPassingRetrievalProcessor.class, HasMarkPassingContext.class, "MarkPassing"); + dataRetrieverChainDefinitions.add(markPassingRetrieverChainDefinition); + + final DataRetrieverChainDefinition legOfCompetitorRetrieverChainDefinition = new SimpleDataRetrieverChainDefinition<>( + trackedRaceRetrieverChainDefinition, HasTrackedLegOfCompetitorContext.class, "LegSailingDomainRetrieverChain"); + legOfCompetitorRetrieverChainDefinition.addAfter(TrackedRaceRetrievalProcessor.class, TrackedLegRetrievalProcessor.class, HasTrackedLegContext.class, "Leg"); + legOfCompetitorRetrieverChainDefinition.endWith(TrackedLegRetrievalProcessor.class, TrackedLegOfCompetitorRetrievalProcessor.class, HasTrackedLegOfCompetitorContext.class, "LegOfCompetitor"); dataRetrieverChainDefinitions.add(legOfCompetitorRetrieverChainDefinition); - DataRetrieverChainDefinition gpsFixRetrieverChainDefinition = new SimpleDataRetrieverChainDefinition<>( - legOfCompetitorRetrieverChainDefinition, HasGPSFixContext.class, "SailingDomainRetrieverChain"); - @SuppressWarnings("unchecked") - Class> gpsFixRetrieverType = (Class>) (Class) GPSFixRetrievalProcessor.class; - gpsFixRetrieverChainDefinition.endWith(legOfCompetitorRetrieverType, gpsFixRetrieverType, + final DataRetrieverChainDefinition gpsFixRetrieverChainDefinition = new SimpleDataRetrieverChainDefinition<>( + legOfCompetitorRetrieverChainDefinition, HasGPSFixContext.class, "GPSFixSailingDomainRetrieverChain"); + gpsFixRetrieverChainDefinition.endWith(TrackedLegOfCompetitorRetrievalProcessor.class, GPSFixRetrievalProcessor.class, HasGPSFixContext.class, "GpsFix"); dataRetrieverChainDefinitions.add(gpsFixRetrieverChainDefinition); } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasGPSFixContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasGPSFixContext.java index f901b18c9ed..0227119e98f 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasGPSFixContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasGPSFixContext.java @@ -1,19 +1,22 @@ package com.sap.sailing.datamining.data; +import java.util.Locale; + import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sse.datamining.shared.annotations.Connector; import com.sap.sse.datamining.shared.annotations.Dimension; import com.sap.sse.datamining.shared.impl.dto.ClusterDTO; +import com.sap.sse.i18n.ResourceBundleStringMessages; public interface HasGPSFixContext { @Connector public HasTrackedLegOfCompetitorContext getTrackedLegOfCompetitorContext(); - @Connector + @Connector(ordinal=1) public GPSFixMoving getGPSFix(); - @Dimension(messageKey="WindStrength", ordinal=10) - public ClusterDTO getWindStrength(); + @Dimension(messageKey="WindStrengthInBeaufort", ordinal=11) + public ClusterDTO getWindStrengthAsBeaufortCluster(Locale locale, ResourceBundleStringMessages stringMessages); } \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasMarkPassingContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasMarkPassingContext.java new file mode 100644 index 00000000000..738e8a28575 --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasMarkPassingContext.java @@ -0,0 +1,29 @@ +package com.sap.sailing.datamining.data; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.common.NoWindException; +import com.sap.sailing.domain.common.Speed; +import com.sap.sailing.domain.tracking.MarkPassing; +import com.sap.sse.datamining.shared.annotations.Connector; +import com.sap.sse.datamining.shared.annotations.Dimension; + +public interface HasMarkPassingContext { + + @Connector + HasTrackedRaceContext getTrackedRaceContext(); + + @Connector + MarkPassing getMarkPassing(); + + @Connector(messageKey="Competitor") + Competitor getCompetitor(); + + @Connector(messageKey="Speed", ordinal=1) + Speed getSpeed(); + + @Connector(messageKey="SpeedTenSecondsBefore", ordinal=2) + Speed getSpeedTenSecondsBefore(); + + @Dimension(messageKey="LegType", ordinal=6) + String getPreviousLegTypeSignifier() throws NoWindException; +} \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/MarkPassingRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/MarkPassingRetrievalProcessor.java new file mode 100644 index 00000000000..4dec85496f6 --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/MarkPassingRetrievalProcessor.java @@ -0,0 +1,47 @@ +package com.sap.sailing.datamining.impl.components; + +import java.util.Collection; +import java.util.NavigableSet; +import java.util.Set; +import java.util.concurrent.ExecutorService; + +import java.util.HashSet; +import com.sap.sailing.datamining.data.HasMarkPassingContext; +import com.sap.sailing.datamining.data.HasTrackedRaceContext; +import com.sap.sailing.datamining.impl.data.MarkPassingWithContext; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.tracking.MarkPassing; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sse.datamining.components.Processor; +import com.sap.sse.datamining.impl.components.AbstractSimpleRetrievalProcessor; + +public class MarkPassingRetrievalProcessor extends + AbstractSimpleRetrievalProcessor { + + public MarkPassingRetrievalProcessor(ExecutorService executor, + Collection> resultReceivers) { + super(HasTrackedRaceContext.class, HasMarkPassingContext.class, executor, resultReceivers); + } + + @Override + protected Iterable retrieveData(HasTrackedRaceContext element) { + TrackedRace trackedRace = element.getTrackedRace(); + Set result = new HashSet<>(); + trackedRace.getRace().getCourse().lockForRead(); + try { + for (Competitor competitor : trackedRace.getRace().getCompetitors()) { + NavigableSet markPassingsForCompetitor = trackedRace.getMarkPassings(competitor); + trackedRace.lockForRead(markPassingsForCompetitor); + try { + markPassingsForCompetitor.stream().forEach(mp->result.add(new MarkPassingWithContext(element, mp))); + } finally { + trackedRace.unlockAfterRead(markPassingsForCompetitor); + } + } + } finally { + trackedRace.getRace().getCourse().unlockAfterRead(); + } + return result; + } + +} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/RegattaLeaderboardFilteringRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/RegattaLeaderboardRetrievalProcessor.java similarity index 88% rename from java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/RegattaLeaderboardFilteringRetrievalProcessor.java rename to java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/RegattaLeaderboardRetrievalProcessor.java index 6dba1811dba..89361bc48a3 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/RegattaLeaderboardFilteringRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/RegattaLeaderboardRetrievalProcessor.java @@ -10,10 +10,10 @@ import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.impl.components.AbstractSimpleRetrievalProcessor; -public class RegattaLeaderboardFilteringRetrievalProcessor extends +public class RegattaLeaderboardRetrievalProcessor extends AbstractSimpleRetrievalProcessor { - public RegattaLeaderboardFilteringRetrievalProcessor(ExecutorService executor, + public RegattaLeaderboardRetrievalProcessor(ExecutorService executor, Collection> resultReceivers) { super(LeaderboardGroup.class, RegattaLeaderboard.class, executor, resultReceivers); } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegOfCompetitorFilteringRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegOfCompetitorRetrievalProcessor.java similarity index 90% rename from java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegOfCompetitorFilteringRetrievalProcessor.java rename to java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegOfCompetitorRetrievalProcessor.java index c412b8d316c..8c25a0029cd 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegOfCompetitorFilteringRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegOfCompetitorRetrievalProcessor.java @@ -11,10 +11,10 @@ import com.sap.sailing.domain.base.Competitor; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.impl.components.AbstractSimpleRetrievalProcessor; -public class TrackedLegOfCompetitorFilteringRetrievalProcessor extends +public class TrackedLegOfCompetitorRetrievalProcessor extends AbstractSimpleRetrievalProcessor { - public TrackedLegOfCompetitorFilteringRetrievalProcessor(ExecutorService executor, + public TrackedLegOfCompetitorRetrievalProcessor(ExecutorService executor, Collection> resultReceivers) { super(HasTrackedLegContext.class, HasTrackedLegOfCompetitorContext.class, executor, resultReceivers); } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegFilteringRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegRetrievalProcessor.java similarity index 91% rename from java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegFilteringRetrievalProcessor.java rename to java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegRetrievalProcessor.java index cffac84007d..2e61e0051a3 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegFilteringRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegRetrievalProcessor.java @@ -11,10 +11,10 @@ import com.sap.sailing.domain.tracking.TrackedLeg; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.impl.components.AbstractSimpleRetrievalProcessor; -public class TrackedLegFilteringRetrievalProcessor extends +public class TrackedLegRetrievalProcessor extends AbstractSimpleRetrievalProcessor { - public TrackedLegFilteringRetrievalProcessor(ExecutorService executor, + public TrackedLegRetrievalProcessor(ExecutorService executor, Collection> resultReceivers) { super(HasTrackedRaceContext.class, HasTrackedLegContext.class, executor, resultReceivers); } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedRaceFilteringRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedRaceRetrievalProcessor.java similarity index 92% rename from java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedRaceFilteringRetrievalProcessor.java rename to java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedRaceRetrievalProcessor.java index ed5617988ee..7800527efda 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedRaceFilteringRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedRaceRetrievalProcessor.java @@ -14,10 +14,10 @@ import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.impl.components.AbstractSimpleRetrievalProcessor; -public class TrackedRaceFilteringRetrievalProcessor extends +public class TrackedRaceRetrievalProcessor extends AbstractSimpleRetrievalProcessor { - public TrackedRaceFilteringRetrievalProcessor(ExecutorService executor, + public TrackedRaceRetrievalProcessor(ExecutorService executor, Collection> resultReceivers) { super(Leaderboard.class, HasTrackedRaceContext.class, executor, resultReceivers); } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/GPSFixWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/GPSFixWithContext.java index fe1d4a2adc4..39219266b1b 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/GPSFixWithContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/GPSFixWithContext.java @@ -1,5 +1,7 @@ package com.sap.sailing.datamining.impl.data; +import java.util.Locale; + import com.sap.sailing.datamining.Activator; import com.sap.sailing.datamining.data.HasGPSFixContext; import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; @@ -7,6 +9,7 @@ import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.Wind; import com.sap.sse.datamining.data.Cluster; import com.sap.sse.datamining.shared.impl.dto.ClusterDTO; +import com.sap.sse.i18n.ResourceBundleStringMessages; public class GPSFixWithContext implements HasGPSFixContext { @@ -31,10 +34,10 @@ public class GPSFixWithContext implements HasGPSFixContext { } @Override - public ClusterDTO getWindStrength() { + public ClusterDTO getWindStrengthAsBeaufortCluster(Locale locale, ResourceBundleStringMessages stringMessages) { Wind wind = getWind(); Cluster cluster = Activator.getDefault().getClusterGroups().getWindStrengthInBeaufortCluster().getClusterFor(wind); - return new ClusterDTO(cluster.toString()); + return new ClusterDTO(cluster.getAsLocalizedString(locale, stringMessages)); } private Wind getWind() { diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/MarkPassingWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/MarkPassingWithContext.java new file mode 100644 index 00000000000..45fb928efee --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/MarkPassingWithContext.java @@ -0,0 +1,74 @@ +package com.sap.sailing.datamining.impl.data; + +import com.sap.sailing.datamining.data.HasMarkPassingContext; +import com.sap.sailing.datamining.data.HasTrackedRaceContext; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.common.NoWindException; +import com.sap.sailing.domain.common.Speed; +import com.sap.sailing.domain.tracking.MarkPassing; +import com.sap.sailing.domain.tracking.TrackedLeg; +import com.sap.sailing.domain.tracking.TrackedRace; + +public class MarkPassingWithContext implements HasMarkPassingContext { + private final HasTrackedRaceContext trackedRaceContext; + private final MarkPassing markPassing; + + public MarkPassingWithContext(HasTrackedRaceContext trackedRaceContext, MarkPassing markPassing) { + super(); + this.trackedRaceContext = trackedRaceContext; + this.markPassing = markPassing; + } + + @Override + public HasTrackedRaceContext getTrackedRaceContext() { + return trackedRaceContext; + } + + @Override + public MarkPassing getMarkPassing() { + return markPassing; + } + + @Override + public Competitor getCompetitor() { + return markPassing.getCompetitor(); + } + + @Override + public Speed getSpeed() { + return getTrackedRace().getTrack(getCompetitor()).getEstimatedSpeed(getMarkPassing().getTimePoint()); + } + + @Override + public Speed getSpeedTenSecondsBefore() { + return getTrackedRace().getTrack(getCompetitor()).getEstimatedSpeed(getMarkPassing().getTimePoint().minus(10000 /* ms */)); + } + + private TrackedRace getTrackedRace() { + return trackedRaceContext.getTrackedRace(); + } + + @Override + public String getPreviousLegTypeSignifier() throws NoWindException { + final String result; + TrackedLeg previousLeg = getPreviousLeg(); + if (previousLeg == null) { + result = "Start"; + } else { + result = previousLeg.getLegType(getMarkPassing().getTimePoint()).toString(); + } + return result; + } + + private TrackedLeg getPreviousLeg() { + final TrackedLeg result; + Waypoint waypoint = markPassing.getWaypoint(); + if (waypoint == getTrackedRace().getRace().getCourse().getFirstWaypoint()) { + result = null; + } else { + result = getTrackedRace().getTrackedLegFinishingAt(waypoint); + } + return result; + } +} diff --git a/java/com.sap.sailing.domain.common/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.common/META-INF/MANIFEST.MF index 7bd94febbcb..ebce249046e 100644 --- a/java/com.sap.sailing.domain.common/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.common/META-INF/MANIFEST.MF @@ -20,6 +20,8 @@ Export-Package: com.sap.sailing.domain.common, com.sap.sailing.domain.common.racelog.tracking, com.sap.sailing.domain.common.racelog.utils, com.sap.sailing.domain.common.scalablevalue.impl, + com.sap.sailing.domain.common.security, com.sap.sailing.domain.common.trackfiles Require-Bundle: com.sap.sse.datamining.shared, - com.sap.sse.common + com.sap.sse.common, + com.sap.sse.security.common diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetGenerationSettings.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetGenerationSettings.java index 9b4805cb0a5..e5a2c4f6de0 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetGenerationSettings.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetGenerationSettings.java @@ -32,4 +32,6 @@ public interface PolarSheetGenerationSettings extends Serializable { boolean areDefault(); + int getNumberOfLeadingCompetitorsToInclude(); + } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetsXYDiagramData.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetsXYDiagramData.java index 45ed2a5328a..0ab886a20b7 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetsXYDiagramData.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetsXYDiagramData.java @@ -11,4 +11,6 @@ public interface PolarSheetsXYDiagramData extends Serializable { List> getPointsForAverageConfidence(Tack tack, LegType legType); + List> getPointsForAverageSpeedRegression(Tack tack, LegType legType); + } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTO.java index 7691619ac39..6b6c4ab773b 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTO.java @@ -22,11 +22,17 @@ public interface CompetitorDTO extends Serializable, MappableToDevice { String getName(); Color getColor(); + + String getEmail(); + + boolean hasEmail(); /** * A regular instance will simply return this object. A compacted version may compute the result by looking it up * from the previous version of the enclosing leaderboard. */ CompetitorDTO getCompetitorFromPrevious(LeaderboardDTO previousVersion); + + String getImageURL(); } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java index 96d77c6614a..edb471564f3 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java @@ -19,22 +19,26 @@ public class CompetitorDTOImpl extends NamedDTO implements CompetitorDTO, Serial private String twoLetterIsoCountryCode; private String threeLetterIocCountryCode; private Color color; + private String email; private String sailID; private String idAsString; private BoatClassDTO boatClass; + private String imageURL; public CompetitorDTOImpl() {} - public CompetitorDTOImpl(String name, Color color, String twoLetterIsoCountryCode, String threeLetterIocCountryCode, - String countryName, String sailID, String idAsString, BoatClassDTO boatClass) { + public CompetitorDTOImpl(String name, Color color, String email, String twoLetterIsoCountryCode, String threeLetterIocCountryCode, + String countryName, String sailID, String idAsString, String imageURL, BoatClassDTO boatClass) { super(name); this.color = color; + this.email = email; this.twoLetterIsoCountryCode = twoLetterIsoCountryCode; this.threeLetterIocCountryCode = threeLetterIocCountryCode; this.countryName = countryName; this.sailID = sailID; this.idAsString = idAsString; this.boatClass = boatClass; + this.imageURL = imageURL; } @Override @@ -45,7 +49,9 @@ public class CompetitorDTOImpl extends NamedDTO implements CompetitorDTO, Serial result = prime * result + ((idAsString == null) ? 0 : idAsString.hashCode()); result = prime * result + ((sailID == null) ? 0 : sailID.hashCode()); result = prime * result + ((color == null) ? 0 : color.hashCode()); + result = prime * result + ((email == null) ? 0 : email.hashCode()); result = prime * result + ((threeLetterIocCountryCode == null) ? 0 : threeLetterIocCountryCode.hashCode()); + result = prime * result + ((imageURL == null) ? 0 : imageURL.hashCode()); return result; } @@ -83,6 +89,16 @@ public class CompetitorDTOImpl extends NamedDTO implements CompetitorDTO, Serial return false; } else if (!color.equals(other.color)) return false; + if (email == null) { + if (other.email != null) + return false; + } else if (!email.equals(other.email)) + return false; + if (imageURL == null) { + if (other.imageURL != null) + return false; + } else if (!imageURL.equals(other.imageURL)) + return false; return true; } @@ -105,6 +121,11 @@ public class CompetitorDTOImpl extends NamedDTO implements CompetitorDTO, Serial public String getSailID() { return sailID; } + + @Override + public String getImageURL() { + return imageURL; + } @Override public String getIdAsString() { @@ -124,4 +145,17 @@ public class CompetitorDTOImpl extends NamedDTO implements CompetitorDTO, Serial public Color getColor() { return color; } + + public String getEmail() { + return email; + } + + public String email(){ + return email; + } + + @Override + public boolean hasEmail() { + return email != null && !email.isEmpty(); + } } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousCompetitorDTOImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousCompetitorDTOImpl.java index 0cee29c1a92..ea7cbc6caec 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousCompetitorDTOImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousCompetitorDTOImpl.java @@ -92,9 +92,26 @@ public class PreviousCompetitorDTOImpl implements CompetitorDTO { throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+ " need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization"); } + + @Override + public String getEmail() { + throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+ + " need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization"); + } + + @Override + public String getImageURL() { + throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+ + " need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization"); + } @Override public CompetitorDTO getCompetitorFromPrevious(LeaderboardDTO previousVersion) { return previousVersion.competitors.get(indexInPreviousCompetitorList); } + + @Override + public boolean hasEmail() { + return false; + } } \ No newline at end of file diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousSuppressedCompetitorDTOImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousSuppressedCompetitorDTOImpl.java index f9d9874b71e..4d1fdbf97d0 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousSuppressedCompetitorDTOImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousSuppressedCompetitorDTOImpl.java @@ -24,6 +24,4 @@ public class PreviousSuppressedCompetitorDTOImpl extends PreviousCompetitorDTOIm public CompetitorDTO getCompetitorFromPrevious(LeaderboardDTO previousVersion) { return Util.get(previousVersion.getSuppressedCompetitors(), getIndexInPreviousCompetitorList()); } - - } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/NaturalComparator.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/NaturalComparator.java index 32713ccc178..94a3d24cba4 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/NaturalComparator.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/NaturalComparator.java @@ -25,7 +25,9 @@ public class NaturalComparator implements Comparator { * Compare the passed strings in natural order. */ @Override - public int compare(String a, String b) { + public int compare(final String aOrNull, final String bOrNull) { + String a = aOrNull == null ? "" : aOrNull; + String b = bOrNull == null ? "" : bOrNull; if (!caseSensitive){ a = a.toLowerCase(); b = b.toLowerCase(); diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetGenerationSettingsImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetGenerationSettingsImpl.java index d1975070b25..8f1ee656f11 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetGenerationSettingsImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetGenerationSettingsImpl.java @@ -11,7 +11,7 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet double[] levels = { 4., 6., 8., 10., 12., 14., 16., 20., 25., 30. }; WindSpeedSteppingWithMaxDistance windStepping = new WindSpeedSteppingWithMaxDistance(levels, 2.5); return new PolarSheetGenerationSettingsImpl(50, 0.1, 20, 20, 0.1, true, true, 2, 0.05, true, windStepping, - false); + false, 0); } public static PolarSheetGenerationSettings createBackendPolarSettings() { @@ -26,7 +26,7 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet } WindSpeedSteppingWithMaxDistance windStepping = new WindSpeedSteppingWithMaxDistance(levels, 0.5); return new PolarSheetGenerationSettingsImpl(50, 0.1, 20, 20, 0.1, true, true, 2, 0.05, true, windStepping, - false); + false, 1); } private static final long serialVersionUID = 2731616509404813790L; @@ -42,6 +42,7 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet private boolean useOnlyEstimationForWindDirection; private WindSpeedSteppingWithMaxDistance windStepping; private boolean splitByWindGauges; + private int numberOfLeadingCompetitorsToInclude; //GWT PolarSheetGenerationSettingsImpl() {}; @@ -51,7 +52,7 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet boolean useOnlyWindGaugesForWindSpeed, boolean shouldRemoveOutliers, double outlierDetectionNeighboorhoodRadius, double outlierMinimumNeighboorhoodPct, boolean useOnlyEstimationForWindDirection, WindSpeedSteppingWithMaxDistance windStepping, - boolean splitByWindGauges) { + boolean splitByWindGauges, int numberOfLeadingCompetitorsToInclude) { this.minimumDataCountPerGraph = minimumDataCountPerGraph; this.minimumWindConfidence = minimumWindConfidence; this.minimumDataCountPerAngle = minimumDataCountPerAngle; @@ -64,6 +65,7 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet this.useOnlyEstimationForWindDirection = useOnlyEstimationForWindDirection; this.windStepping = windStepping; this.splitByWindGauges = splitByWindGauges; + this.numberOfLeadingCompetitorsToInclude = numberOfLeadingCompetitorsToInclude; } @Override @@ -206,5 +208,10 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet return true; } + @Override + public int getNumberOfLeadingCompetitorsToInclude() { + return numberOfLeadingCompetitorsToInclude; + } + } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetsXYDiagramDataImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetsXYDiagramDataImpl.java index f91a70af391..8fb3db96bfe 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetsXYDiagramDataImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetsXYDiagramDataImpl.java @@ -1,6 +1,5 @@ package com.sap.sailing.domain.common.impl; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -17,35 +16,18 @@ public class PolarSheetsXYDiagramDataImpl implements PolarSheetsXYDiagramData { } private Map, List>> pointsForAverageSpeedMovingAverage; + + private Map, List>> pointsForAverageSpeedRegression; private Map, List>> pointsForAverageConfidence; - public PolarSheetsXYDiagramDataImpl(List> pointsForUpwindStarboardAverageSpeedMovingAverage, - List> pointsForUpwindStarboardAverageConfidence, - List> pointsForUpwindPortAverageSpeedMovingAverage, - List> pointsForUpwindPortAverageConfidence, - List> pointsForDownwindStarboardAverageSpeedMovingAverage, - List> pointsForDownwindStarboardAverageConfidence, - List> pointsForDownwindPortAverageSpeedMovingAverage, - List> pointsForDownwindPortAverageConfidence) { - pointsForAverageSpeedMovingAverage = new HashMap, List>>(); - pointsForAverageConfidence = new HashMap, List>>(); - pointsForAverageSpeedMovingAverage.put(new Pair(LegType.UPWIND, Tack.STARBOARD), - pointsForUpwindStarboardAverageSpeedMovingAverage); - pointsForAverageConfidence.put(new Pair(LegType.UPWIND, Tack.STARBOARD), - pointsForUpwindStarboardAverageConfidence); - pointsForAverageSpeedMovingAverage.put(new Pair(LegType.UPWIND, Tack.PORT), - pointsForUpwindPortAverageSpeedMovingAverage); - pointsForAverageConfidence.put(new Pair(LegType.UPWIND, Tack.PORT), - pointsForUpwindPortAverageConfidence); - pointsForAverageSpeedMovingAverage.put(new Pair(LegType.DOWNWIND, Tack.STARBOARD), - pointsForDownwindStarboardAverageSpeedMovingAverage); - pointsForAverageConfidence.put(new Pair(LegType.DOWNWIND, Tack.STARBOARD), - pointsForDownwindStarboardAverageConfidence); - pointsForAverageSpeedMovingAverage.put(new Pair(LegType.DOWNWIND, Tack.PORT), - pointsForDownwindPortAverageSpeedMovingAverage); - pointsForAverageConfidence.put(new Pair(LegType.DOWNWIND, Tack.PORT), - pointsForDownwindPortAverageConfidence); + public PolarSheetsXYDiagramDataImpl( + Map, List>> movingAverageSpeedDataLists, + Map, List>> regressionSpeedDataLists, + Map, List>> averageConfidenceDataLists) { + pointsForAverageSpeedMovingAverage = movingAverageSpeedDataLists; + pointsForAverageSpeedRegression = regressionSpeedDataLists; + pointsForAverageConfidence = averageConfidenceDataLists; } @Override @@ -58,4 +40,8 @@ public class PolarSheetsXYDiagramDataImpl implements PolarSheetsXYDiagramData { return pointsForAverageConfidence.get(new Pair(legType, tack)); } + @Override + public List> getPointsForAverageSpeedRegression(Tack tack, LegType legType) { + return pointsForAverageSpeedRegression.get(new Pair(legType, tack)); + } } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/DeviceMappingConstants.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/DeviceMappingConstants.java index affb0ce5572..5e468744ffb 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/DeviceMappingConstants.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/DeviceMappingConstants.java @@ -1,5 +1,8 @@ package com.sap.sailing.domain.common.racelog.tracking; +import com.sap.sailing.domain.common.racelog.RaceLogServletConstants; +import com.sap.sse.common.util.UrlHelper; + /** * Shared between GWT and Android. Used for creating and deciphering the URL encoded by the QRCode, which gives the * tracking app all necessary information for creating the device mapping of either the race or leaderboard/regatta. @@ -13,28 +16,50 @@ package com.sap.sailing.domain.common.racelog.tracking; * * @author Fredrik Teschke */ -public interface DeviceMappingConstants { +public class DeviceMappingConstants { // According to the HTTP protocol definition (http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.2.3), only // schema and host are case insensitive. // But to adhere to best practices, and due to the deficiencies of some web servers and clients, // all URL components should be treated as case insensitive, preferring _underscores_ to CamelCase. - static final String URL_BASE = "/tracking/checkin"; - static final String URL_EVENT_ID = "event_id"; - static final String URL_LEADERBOARD_NAME = "leaderboard_name"; - static final String URL_COMPETITOR_ID_AS_STRING = "competitor_id"; - static final String URL_MARK_ID_AS_STRING = "mark_id"; - static final String URL_FROM_MILLIS = "from_millis"; - static final String URL_TO_MILLIS = "to_millis"; + public static final String URL_BASE = "/tracking/checkin"; + public static final String URL_EVENT_ID = "event_id"; + public static final String URL_LEADERBOARD_NAME = "leaderboard_name"; + public static final String URL_COMPETITOR_ID_AS_STRING = "competitor_id"; + public static final String URL_MARK_ID_AS_STRING = "mark_id"; + public static final String URL_FROM_MILLIS = "from_millis"; + public static final String URL_TO_MILLIS = "to_millis"; - static final String JSON_COMPETITOR_ID_AS_STRING = "competitorId"; - static final String JSON_MARK_ID_AS_STRING = "markId"; - static final String JSON_DEVICE_UUID = "deviceUuid"; - static final String JSON_DEVICE_TYPE = "deviceType"; - static final String JSON_PUSH_DEVICE_ID = "pushDeviceId"; - static final String JSON_FROM_MILLIS = "fromMillis"; - static final String JSON_TO_MILLIS = "toMillis"; - static final String JSON_TEAM_IMAGE_URI = "teamImageUri"; + public static final String JSON_COMPETITOR_ID_AS_STRING = "competitorId"; + public static final String JSON_MARK_ID_AS_STRING = "markId"; + public static final String JSON_DEVICE_UUID = "deviceUuid"; + public static final String JSON_DEVICE_TYPE = "deviceType"; + public static final String JSON_PUSH_DEVICE_ID = "pushDeviceId"; + public static final String JSON_FROM_MILLIS = "fromMillis"; + public static final String JSON_TO_MILLIS = "toMillis"; + public static final String JSON_TEAM_IMAGE_URI = "teamImageUri"; @Deprecated static final String APK_PATH = "/apps/com.sap.sailing.android.tracking.app.apk"; + + public static String getDeviceMappingForRegattaLogUrl(String serverUrlWithoutTrailingSlash, String eventId, + String leaderboardName, String mappedItemType, String mappedItemId, UrlHelper helper) { + return helper.encodeUrl(serverUrlWithoutTrailingSlash + URL_BASE + "?" + URL_EVENT_ID + "=" + eventId + "&" + + URL_LEADERBOARD_NAME + "=" + leaderboardName + "&" + mappedItemType + "=" + + mappedItemId); + } + + @Deprecated + public static String getDeviceMappingForRaceLogUrl(String serverUrlWithoutTrailingSlash, String leaderboardName, + String raceColumnName, String fleetName, String mappedItemType, String mappedItemId, long fromMillis, + long toMillis, UrlHelper helper) throws QRCodeURLCreationException { + if (fromMillis > toMillis) { + throw new QRCodeURLCreationException("from can't lie after to"); + } + return helper.encodeUrl(serverUrlWithoutTrailingSlash + DeviceMappingConstants.APK_PATH + "?" + + RaceLogServletConstants.PARAMS_LEADERBOARD_NAME + "=" + leaderboardName + "&" + + RaceLogServletConstants.PARAMS_RACE_COLUMN_NAME + "=" + raceColumnName + "&" + + RaceLogServletConstants.PARAMS_RACE_FLEET_NAME + "=" + fleetName + "&" + mappedItemType + "=" + + mappedItemId + "&" + DeviceMappingConstants.URL_FROM_MILLIS + "=" + fromMillis + "&" + + DeviceMappingConstants.URL_TO_MILLIS + "=" + toMillis); + } } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/QRCodeURLCreationException.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/QRCodeURLCreationException.java new file mode 100644 index 00000000000..aa2e0847644 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/QRCodeURLCreationException.java @@ -0,0 +1,9 @@ +package com.sap.sailing.domain.common.racelog.tracking; + +public class QRCodeURLCreationException extends Exception { + private static final long serialVersionUID = -8243995470804772458L; + + public QRCodeURLCreationException(String message) { + super(message); + } +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/Permission.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/Permission.java new file mode 100755 index 00000000000..0a542a58534 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/Permission.java @@ -0,0 +1,29 @@ +package com.sap.sailing.domain.common.security; + +public enum Permission implements com.sap.sse.security.shared.Permission { + MANAGE_EVENTS, + MANAGE_REGATTAS, + MANAGE_TRACKED_RACES, + SHOW_TRACKED_RACES, + MANAGE_RACELOG_TRACKING, + MANAGE_ALL_COMPETITORS, + MANAGE_COURSE_LAYOUT, + MANAGE_WIND, + MANAGE_IGTIMI_ACCOUNTS, + MANAGE_LEADERBOARDS, + MANAGE_LEADERBOARD_GROUPS, + MANAGE_RESULT_IMPORT_URLS, + MANAGE_STRUCTURE_IMPORT_URLS, + MANAGE_MEDIA, + MANAGE_SAILING_SERVER_INSTANCES, + MANAGE_REPLICATION, + MANAGE_MASTERDATA_IMPORT, + MANAGE_DEVICE_CONFIGURATION, + MANAGE_USERS, + MANAGE_FILE_STORAGE; + + @Override + public String getStringPermission() { + return name()+":"; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/usermanagement/UserRoles.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/Roles.java similarity index 57% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/usermanagement/UserRoles.java rename to java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/Roles.java index a6c432b87fc..5d54d40edd2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/usermanagement/UserRoles.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/Roles.java @@ -1,9 +1,8 @@ -package com.sap.sailing.gwt.ui.usermanagement; +package com.sap.sailing.domain.common.security; -import com.sap.sse.security.shared.DefaultRoles; +import com.sap.sse.security.shared.Role; -public enum UserRoles { - administrator(DefaultRoles.ADMIN.getRolename()), +public enum Roles implements Role { spectator("spectator"), moderator("moderator"), sailor("sailor"), @@ -11,7 +10,7 @@ public enum UserRoles { eventmanager("eventmanager"), mediaeditor("mediaeditor"); - private UserRoles(String rolename) { + private Roles(String rolename) { this.rolename = rolename; } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/SailingPermissionsForRoleProvider.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/SailingPermissionsForRoleProvider.java new file mode 100755 index 00000000000..7300e408977 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/SailingPermissionsForRoleProvider.java @@ -0,0 +1,21 @@ +package com.sap.sailing.domain.common.security; + +import java.util.Collections; + +import com.sap.sse.security.shared.DefaultRoles; +import com.sap.sse.security.shared.PermissionsForRoleProvider; + +public class SailingPermissionsForRoleProvider implements PermissionsForRoleProvider { + public static final SailingPermissionsForRoleProvider INSTANCE = new SailingPermissionsForRoleProvider(); + + @Override + public Iterable getPermissions(String role) { + final Iterable result; + if (DefaultRoles.ADMIN.getRolename().equals(role)) { + return Collections.singletonList("*"); + } else { + result = Collections.emptyList(); + } + return result; + } +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/AbstractGPSFixStoreTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/AbstractGPSFixStoreTest.java index e15e16f2a28..e6cb2e8c28c 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/AbstractGPSFixStoreTest.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/AbstractGPSFixStoreTest.java @@ -40,7 +40,7 @@ public class AbstractGPSFixStoreTest { protected final DeviceIdentifier device = new SmartphoneImeiIdentifier("a"); protected RaceLog raceLog; protected GPSFixStore store; - protected final Competitor comp = DomainFactory.INSTANCE.getOrCreateCompetitor("comp", "comp", null, null, null); + protected final Competitor comp = DomainFactory.INSTANCE.getOrCreateCompetitor("comp", "comp", null, null, null, null); protected final Mark mark = DomainFactory.INSTANCE.getOrCreateMark("mark"); private final AbstractLogEventAuthor author = new LogEventAuthorImpl("author", 0); diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java index bff12901d90..f124610bc27 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java @@ -168,7 +168,7 @@ public class CreateAndTrackWithRaceLogTest { assertFalse(raceLog.isEmpty()); // add a mapping and one fix in, one out of mapping - Competitor comp1 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp1", "comp1", null, null, null); + Competitor comp1 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp1", "comp1", null, null, null, null); DeviceIdentifier dev1 = new SmartphoneImeiIdentifier("dev1"); raceLog.add(factory.createDeviceCompetitorMappingEvent(t(), author, dev1, comp1, 0, t(0), t(10))); addFixes0(dev1); @@ -202,7 +202,7 @@ public class CreateAndTrackWithRaceLogTest { adapter.denoteRaceForRaceLogTracking(service, leaderboard, column, fleet, "race"); // add a mapping and one fix in, one out of mapping - Competitor comp1 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp1", "comp1", null, null, null); + Competitor comp1 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp1", "comp1", null, null, null, null); DeviceIdentifier dev1 = new SmartphoneImeiIdentifier("dev1"); regattaLog.add(new RegattaLogDeviceCompetitorMappingEventImpl(t(), author, t(), UUID.randomUUID(), comp1, dev1, t(0), t(10))); diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceLoadsFixesTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceLoadsFixesTest.java index 0647e02d62a..e4c276dd8ce 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceLoadsFixesTest.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceLoadsFixesTest.java @@ -49,7 +49,7 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest { @Test public void areFixesStoredInDb() throws TransformationException, NoCorrespondingServiceRegisteredException, InterruptedException { - Competitor comp2 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp2", "comp2", null, null, null); + Competitor comp2 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp2", "comp2", null, null, null, null); Mark mark2 = DomainFactory.INSTANCE.getOrCreateMark("mark2"); DeviceIdentifier device2 = new SmartphoneImeiIdentifier("imei2"); DeviceIdentifier device3 = new SmartphoneImeiIdentifier("imei3"); diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/.classpath b/java/com.sap.sailing.domain.racelogtrackingadapter/.classpath index 4f83b2397ec..03c4ce9fdd8 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/.classpath +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/.classpath @@ -2,6 +2,7 @@ + diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/.settings/org.eclipse.core.resources.prefs b/java/com.sap.sailing.domain.racelogtrackingadapter/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000000..1b6fd8593f0 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding//resources/stringmessages/TrackingStringMessages_de.properties=UTF-8 diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.racelogtrackingadapter/META-INF/MANIFEST.MF index b16960adcc4..5133df9643f 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/META-INF/MANIFEST.MF @@ -17,8 +17,13 @@ Require-Bundle: com.sap.sailing.domain, com.sap.sse.common, com.sap.sse.replication, com.sap.sse, - com.sap.sailing.server.gateway.serialization.shared.android -Import-Package: com.mongodb, + com.sap.sailing.server.gateway.serialization.shared.android, + com.sap.sse.mail +Import-Package: com.google.zxing;version="3.1.0", + com.mongodb, + javax.mail;version="1.4.0", + javax.mail.internet;version="1.4.0", + javax.mail.util;version="1.4.0", javax.servlet;version="2.6.0", org.json.simple, org.json.simple.parser, diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/build.properties b/java/com.sap.sailing.domain.racelogtrackingadapter/build.properties index 34d2e4d2dad..f11210d3f2c 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/build.properties +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/build.properties @@ -1,4 +1,5 @@ -source.. = src/ +source.. = src/,\ + resources/ output.. = bin/ bin.includes = META-INF/,\ . diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_de.properties b/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_de.properties new file mode 100644 index 00000000000..21a7ad3191e --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_de.properties @@ -0,0 +1,4 @@ +trackingInvitationFor=Einladung zum Tracken für +scanQRCodeOrVisitUrlToRegisterAs=Scanne den QRCode oder klicke den Link mit deinem Smartphone an um dich zu registrieren als +welcomeTo=Willkommen zu +alternativelyVisitThisLink=Alternativ kannst du diesen Link anklicken \ No newline at end of file diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_en.properties b/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_en.properties new file mode 100644 index 00000000000..4edebbdcda5 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_en.properties @@ -0,0 +1,4 @@ +trackingInvitationFor=Tracking Invitation for +scanQRCodeOrVisitUrlToRegisterAs=Scan the following QRCode or click the link using your smartphone to register as +welcomeTo=Welcome to +alternativelyVisitThisLink=Alternatively, you can click this Link \ No newline at end of file diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapter.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapter.java index 56b300f4aca..b6efc960f89 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapter.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapter.java @@ -1,5 +1,6 @@ package com.sap.sailing.domain.racelogtracking; +import java.util.Locale; import java.util.Set; import com.sap.sailing.domain.abstractlog.race.RaceLog; @@ -10,6 +11,7 @@ import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogStartTrackingEven import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.ControlPoint; +import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.RaceColumn; @@ -24,12 +26,14 @@ import com.sap.sailing.domain.racelogtracking.impl.RaceLogRaceTracker; import com.sap.sailing.domain.tracking.GPSFix; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.server.RacingEventService; +import com.sap.sse.common.mail.MailException; -public interface RaceLogTrackingAdapter { +public interface RaceLogTrackingAdapter { /** - * Performs the necessary steps to ensure that the race is tracked (aka that a {@link TrackedRace} - * is created from the data in this {@code RaceLog}).

- * The following steps are performed to achieve this: + * Performs the necessary steps to ensure that the race is tracked (aka that a {@link TrackedRace} is created from + * the data in this {@code RaceLog}). + *

+ * The following steps are performed to achieve this: *

    *
  • Is the racelog denoted for tracking? If not, throw exception.
  • *
  • Is a {@link RaceLogRaceTracker} already listening for this racelog? If not, add one.
  • @@ -38,7 +42,7 @@ public interface RaceLogTrackingAdapter { */ void startTracking(RacingEventService service, Leaderboard leaderboard, RaceColumn raceColumn, Fleet fleet) throws NotDenotedForRaceLogTrackingException, Exception; - + RaceLogTrackingState getRaceLogTrackingState(RacingEventService service, RaceColumn raceColumn, Fleet fleet); /** @@ -51,16 +55,15 @@ public interface RaceLogTrackingAdapter { * * @throws NotDenotableForRaceLogTrackingException * Fails, if no {@link RaceLog}, or a non-empty {@link RaceLog}, or one with attached - * {@link TrackedRace} is found already in place. - * Also fails, if the {@code leaderboard} is not a {@link RegattaLeaderboard}. + * {@link TrackedRace} is found already in place. Also fails, if the {@code leaderboard} is not a + * {@link RegattaLeaderboard}. */ void denoteRaceForRaceLogTracking(RacingEventService service, Leaderboard leaderboard, RaceColumn raceColumn, Fleet fleet, String raceName) throws NotDenotableForRaceLogTrackingException; - + /** - * Revoke the {@link RaceLogDenoteForTrackingEvent}, and if it exists the {@link RaceLogStartTrackingEvent}. - * This does not affect existing an {@link RaceLogRaceTracker} - * or {@link TrackedRace} for this {@code RaceLog}. + * Revoke the {@link RaceLogDenoteForTrackingEvent}, and if it exists the {@link RaceLogStartTrackingEvent}. This + * does not affect existing an {@link RaceLogRaceTracker} or {@link TrackedRace} for this {@code RaceLog}. */ void removeDenotationForRaceLogTracking(RacingEventService service, RaceLog raceLog); @@ -71,30 +74,35 @@ public interface RaceLogTrackingAdapter { */ void denoteAllRacesForRaceLogTracking(RacingEventService service, Leaderboard leaderboard) throws NotDenotableForRaceLogTrackingException; - + /** * Add a fix to the {@link GPSFixStore}, and create a mapping with a virtual device for exactly that timepoint. */ void pingMark(RaceLog raceLogToAddTo, Mark mark, GPSFix gpsFix, RacingEventService service); - + /** - * Duplicate the course and competitor registrations in the newest {@link RaceLogCourseDesignChangedEvent} - * in {@code from} race log to the {@code to} race logs. - * The {@link Mark}s and {@link ControlPoint}s are duplicated and not reused. This also inserts the necessary - * {@link RaceLogDefineMarkEvent}s into the {@code to} race logs. + * Duplicate the course and competitor registrations in the newest {@link RaceLogCourseDesignChangedEvent} in + * {@code from} race log to the {@code to} race logs. The {@link Mark}s and {@link ControlPoint}s are duplicated and + * not reused. This also inserts the necessary {@link RaceLogDefineMarkEvent}s into the {@code to} race logs. */ void copyCourseAndCompetitors(RaceLog from, Set to, SharedDomainFactory baseDomainFactory, RacingEventService service); - + /** - * If not yet registered, register the competitors in {@code competitors}, - * and unregister all already registered competitors not in {@code competitors}. + * If not yet registered, register the competitors in {@code competitors}, and unregister all already registered + * competitors not in {@code competitors}. */ void registerCompetitors(RacingEventService service, RaceLog raceLog, Set competitors); - + /** - * If not yet registered, register the competitors in {@code competitors}, - * and unregister all already registered competitors not in {@code competitors}. + * If not yet registered, register the competitors in {@code competitors}, and unregister all already registered + * competitors not in {@code competitors}. */ void registerCompetitors(RacingEventService service, RegattaLog regattaLog, Set competitors); + + /** + * Invite competitors for tracking via the Tracking App by sending out emails. + */ + void inviteCompetitorsForTrackingViaEmail(Event event, Leaderboard leaderboard, + String serverUrlWithoutTrailingSlash, Set competitors, Locale locale) throws MailException; } diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/Activator.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/Activator.java index c66d80ac9c5..e26a03e2f62 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/Activator.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/Activator.java @@ -36,6 +36,12 @@ import com.sap.sse.common.TypeBasedServiceFinder; public class Activator implements BundleActivator { private static final Logger logger = Logger.getLogger(Activator.class.getName()); + + private static BundleContext context; + + public static BundleContext getContext() { + return context; + } private Set> registrations = new HashSet<>(); @@ -53,6 +59,8 @@ public class Activator implements BundleActivator { @Override public void start(BundleContext context) throws Exception { + Activator.context = context; + registrations.add(context.registerService(DeviceIdentifierMongoHandler.class, new SmartphoneUUIDMongoHandler(), getDict(SmartphoneUUIDIdentifier.TYPE))); registrations.add(context.registerService(DeviceIdentifierJsonHandler.class, new SmartphoneUUIDJsonHandler(), getDict(SmartphoneUUIDIdentifier.TYPE))); registrations.add(context.registerService(DeviceIdentifierStringSerializationHandler.class, new SmartphoneUUIDStringSerializationHandler(), getDict(SmartphoneUUIDIdentifier.TYPE))); @@ -75,6 +83,8 @@ public class Activator implements BundleActivator { @Override public void stop(BundleContext context) throws Exception { + Activator.context = null; + for (ServiceRegistration reg : registrations) { reg.unregister(); } diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java index 07317ec94ed..c321270dc4d 100755 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java @@ -1,8 +1,11 @@ package com.sap.sailing.domain.racelogtracking.impl; +import java.io.IOException; +import java.io.InputStream; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -10,6 +13,17 @@ import java.util.function.Function; import java.util.logging.Level; import java.util.logging.Logger; +import javax.activation.DataHandler; +import javax.activation.DataSource; +import javax.mail.BodyPart; +import javax.mail.MessagingException; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMultipart; +import javax.mail.util.ByteArrayDataSource; + +import org.osgi.framework.ServiceReference; + +import com.google.zxing.WriterException; import com.sap.sailing.domain.abstractlog.AbstractLog; import com.sap.sailing.domain.abstractlog.AbstractLogEvent; import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; @@ -32,6 +46,7 @@ import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.ControlPoint; import com.sap.sailing.domain.base.CourseBase; import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.RaceColumn; @@ -42,6 +57,7 @@ import com.sap.sailing.domain.base.impl.CourseDataImpl; import com.sap.sailing.domain.common.PassingInstruction; import com.sap.sailing.domain.common.RegattaIdentifier; import com.sap.sailing.domain.common.abstractlog.NotRevokableException; +import com.sap.sailing.domain.common.racelog.tracking.DeviceMappingConstants; import com.sap.sailing.domain.common.racelog.tracking.NotDenotableForRaceLogTrackingException; import com.sap.sailing.domain.common.racelog.tracking.NotDenotedForRaceLogTrackingException; import com.sap.sailing.domain.common.racelog.tracking.RaceLogRaceTrackerExistsException; @@ -61,6 +77,10 @@ import com.sap.sse.common.NoCorrespondingServiceRegisteredException; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.common.mail.MailException; +import com.sap.sse.mail.MailService; +import com.sap.sse.qrcode.QRCodeGenerationUtil; +import com.sap.sse.util.impl.NonGwtUrlHelper; public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter { private static final Logger logger = Logger.getLogger(RaceLogTrackingAdapterImpl.class.getName()); @@ -325,4 +345,66 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter { c -> new RegattaLogRegisterCompetitorEventImpl(MillisecondsTimePoint.now(), service.getServerAuthor(), MillisecondsTimePoint.now(), UUID.randomUUID(), c)); } + + private MailService getMailService() { + ServiceReference ref = Activator.getContext() + .getServiceReference(MailService.class); + if (ref == null) { + logger.warning("No file storage management service registered"); + return null; + } + return Activator.getContext().getService(ref); + } + + @Override + public void inviteCompetitorsForTrackingViaEmail(Event event, Leaderboard leaderboard, + String serverUrlWithoutTrailingSlash, Set competitors, Locale locale) throws MailException { + StringBuilder occuredExceptions = new StringBuilder(); + + for (Competitor competitor : competitors){ + final String toAddress = competitor.getEmail(); + if (toAddress != null) { + String leaderboardName = leaderboard.getName(); + String competitorName = competitor.getName(); + + String url = DeviceMappingConstants.getDeviceMappingForRegattaLogUrl(serverUrlWithoutTrailingSlash, + event.getId().toString(), leaderboardName, DeviceMappingConstants.URL_COMPETITOR_ID_AS_STRING, + competitor.getId().toString(), NonGwtUrlHelper.INSTANCE); + String subject = String.format("%s %s", + RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "trackingInvitationFor"), competitorName); + + // taken from http://www.tutorialspoint.com/javamail_api/javamail_api_send_inlineimage_in_email.htm + BodyPart messageTextPart = new MimeBodyPart(); + String htmlText = String.format("

    %s %s

    " + "

    %s %s

    " + + "
    " + + "%s", + RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "welcomeTo"), leaderboardName, + RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "scanQRCodeOrVisitUrlToRegisterAs"), competitorName, + url, url, RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "alternativelyVisitThisLink")); + + try { + messageTextPart.setContent(htmlText, "text/html"); + + BodyPart messageImagePart = new MimeBodyPart(); + InputStream imageIs = QRCodeGenerationUtil.create(url, 250); + DataSource imageDs = new ByteArrayDataSource(imageIs, "image/png"); + messageImagePart.setDataHandler(new DataHandler(imageDs)); + messageImagePart.setHeader("Content-ID", ""); + + MimeMultipart multipart = new MimeMultipart(); + multipart.addBodyPart(messageTextPart); + multipart.addBodyPart(messageImagePart); + + getMailService().sendMail(toAddress, subject, multipart); + } catch (MessagingException | MailException | WriterException | IOException e) { + logger.log(Level.SEVERE, "Error trying to send mail to " + competitor.getName() + + " with e-mail address " + toAddress, e); + occuredExceptions.append(e.getMessage()+"\r\n"); + } + } + } + if (!(occuredExceptions.length() == 0)){ + throw new MailException(occuredExceptions.toString()); + } + } } diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingI18n.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingI18n.java new file mode 100644 index 00000000000..0a3fa3a6876 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingI18n.java @@ -0,0 +1,11 @@ +package com.sap.sailing.domain.racelogtracking.impl; + +import com.sap.sse.i18n.ResourceBundleStringMessages; +import com.sap.sse.i18n.impl.ResourceBundleStringMessagesImpl; + +public class RaceLogTrackingI18n { + private static final String RESOURCE_BASE_NAME = "stringmessages/TrackingStringMessages"; + + public static final ResourceBundleStringMessages STRING_MESSAGES = new ResourceBundleStringMessagesImpl( + RESOURCE_BASE_NAME, RaceLogTrackingI18n.class.getClassLoader()); +} diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/MultiLogAnalyzerTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/MultiLogAnalyzerTest.java index 60dbfaa4d6d..fe618620b4c 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/MultiLogAnalyzerTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/MultiLogAnalyzerTest.java @@ -52,7 +52,7 @@ public class MultiLogAnalyzerTest { private AbstractLogEventAuthor author; private static Competitor createCompetitor(int n) { - return new CompetitorImpl(n, n + "", null, null, null); + return new CompetitorImpl(n, n + "", null, null, null, null); } private static DeviceIdentifier createDevice() { diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/tracking/analyzing/test/DeviceMappingFinderTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/tracking/analyzing/test/DeviceMappingFinderTest.java index b564043ec86..8eb28322665 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/tracking/analyzing/test/DeviceMappingFinderTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/tracking/analyzing/test/DeviceMappingFinderTest.java @@ -22,8 +22,8 @@ import com.sap.sse.common.TimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint; public class DeviceMappingFinderTest extends AbstractRaceLogTrackingTest { - private final Competitor competitor = new CompetitorImpl("comp", "Comp", null, null, null); - private final Competitor competitor2 = new CompetitorImpl("comp2", "Comp2", null, null, null); + private final Competitor competitor = new CompetitorImpl("comp", "Comp", null, null, null, null); + private final Competitor competitor2 = new CompetitorImpl("comp2", "Comp2", null, null, null, null); private final DeviceIdentifier device = new SmartphoneImeiIdentifier("imei"); private int time = 0; diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Competitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Competitor.java index fea24c2afea..8d65ec10e49 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Competitor.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Competitor.java @@ -15,6 +15,10 @@ public interface Competitor extends Named, WithID, IsManagedByCache { - @Dimension(messageKey="NationalityAcronym") + @Dimension(messageKey="Acronym") String getThreeLetterIOCAcronym(); CountryCode getCountryCode(); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/WithNationality.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/WithNationality.java index c145cdb6e59..33234b9a7c0 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/WithNationality.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/WithNationality.java @@ -3,6 +3,6 @@ package com.sap.sailing.domain.base; import com.sap.sse.datamining.shared.annotations.Connector; public interface WithNationality { - @Connector(ordinal=7) + @Connector(messageKey="Nationality", ordinal=8) Nationality getNationality(); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CompetitorImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CompetitorImpl.java index 031a50935a1..10d8f1bd8e1 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CompetitorImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CompetitorImpl.java @@ -20,13 +20,15 @@ public class CompetitorImpl implements DynamicCompetitor { private String name; private Color color; private transient Set listeners; + private String email; - public CompetitorImpl(Serializable id, String name, Color color, DynamicTeam team, DynamicBoat boat) { + public CompetitorImpl(Serializable id, String name, Color color, String email, DynamicTeam team, DynamicBoat boat) { this.id = id; this.name = name; this.team = team; this.boat = boat; this.color = color; + this.email = email; this.listeners = new HashSet(); } @@ -73,7 +75,7 @@ public class CompetitorImpl implements DynamicCompetitor { @Override public Competitor resolve(SharedDomainFactory domainFactory) { - Competitor result = domainFactory.getOrCreateCompetitor(getId(), getName(), getColor(), getTeam(), getBoat()); + Competitor result = domainFactory.getOrCreateCompetitor(getId(), getName(), getColor(), getEmail(), getTeam(), getBoat()); return result; } @@ -114,4 +116,22 @@ public class CompetitorImpl implements DynamicCompetitor { return new HashSet(listeners); } } + + public String getEmail() { + return email; + } + + public void setEmail(String newEmail) { + final String oldEmail = this.email; + if (!Util.equalsWithNull(oldEmail, newEmail)) { + this.email = newEmail; + for (CompetitorChangeListener listener : getListeners()) { + listener.emailChanged(oldEmail, newEmail); + } + } + } + + public boolean hasEmail(){ + return email != null && !email.isEmpty(); + } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicCompetitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicCompetitor.java index 768c6f15255..4b6a86578f0 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicCompetitor.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicCompetitor.java @@ -6,7 +6,10 @@ import com.sap.sse.common.Color; public interface DynamicCompetitor extends Competitor, Renamable { DynamicBoat getBoat(); + DynamicTeam getTeam(); - + void setColor(Color displayColor); + + void setEmail(String email); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/SharedDomainFactoryImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/SharedDomainFactoryImpl.java index 0d6a8cd5f42..3e6a0ecf5d0 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/SharedDomainFactoryImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/SharedDomainFactoryImpl.java @@ -332,11 +332,11 @@ public class SharedDomainFactoryImpl implements SharedDomainFactory { } @Override - public Competitor getOrCreateCompetitor(Serializable competitorId, String name, Color displayColor, DynamicTeam team, DynamicBoat boat) { + public Competitor getOrCreateCompetitor(Serializable competitorId, String name, Color displayColor, String email, DynamicTeam team, DynamicBoat boat) { if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "getting or creating competitor "+name+" with ID "+competitorId+" in domain factory "+this); } - return getCompetitorStore().getOrCreateCompetitor(competitorId, name, displayColor, team, boat); + return getCompetitorStore().getOrCreateCompetitor(competitorId, name, displayColor, email, team, boat); } @Override diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java index 16051ae3835..6caca851d02 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java @@ -68,8 +68,8 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab listeners.remove(listener); } - private Competitor createCompetitor(Serializable id, String name, Color displayColor, DynamicTeam team, DynamicBoat boat) { - Competitor result = new CompetitorImpl(id, name, displayColor, team, boat); + private Competitor createCompetitor(Serializable id, String name, Color displayColor, String email, DynamicTeam team, DynamicBoat boat) { + Competitor result = new CompetitorImpl(id, name, displayColor, email, team, boat); addNewCompetitor(id, result); if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "Created competitor "+name+" with ID "+id, new Exception("Here is where it happened")); @@ -93,20 +93,20 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab } @Override - public Competitor getOrCreateCompetitor(Serializable competitorId, String name, Color displayColor, DynamicTeam team, DynamicBoat boat) { + public Competitor getOrCreateCompetitor(Serializable competitorId, String name, Color displayColor, String email, DynamicTeam team, DynamicBoat boat) { Competitor result = getExistingCompetitorById(competitorId); // avoid synchronization for successful read access if (result == null) { LockUtil.lockForWrite(lock); try { result = getExistingCompetitorById(competitorId); // try again, now while holding the write lock if (result == null) { - result = createCompetitor(competitorId, name, displayColor, team, boat); + result = createCompetitor(competitorId, name, displayColor, email, team, boat); } } finally { LockUtil.unlockAfterWrite(lock); } } else if (isCompetitorToUpdateDuringGetOrCreate(result)) { - updateCompetitor(result.getId().toString(), name, displayColor, boat.getSailID(), team.getNationality(), + updateCompetitor(result.getId().toString(), name, displayColor, email, boat.getSailID(), team.getNationality(), team.getImage()); competitorNoLongerToUpdateDuringGetOrCreate(result); } @@ -191,7 +191,7 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab } @Override - public Competitor updateCompetitor(String idAsString, String newName, Color newDisplayColor, String newSailId, + public Competitor updateCompetitor(String idAsString, String newName, Color newDisplayColor, String newEmail, String newSailId, Nationality newNationality, URI newTeamImageUri) { DynamicCompetitor competitor = (DynamicCompetitor) getExistingCompetitorByIdAsString(idAsString); if (competitor != null) { @@ -199,6 +199,7 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab try { competitor.setName(newName); competitor.setColor(newDisplayColor); + competitor.setEmail(newEmail); competitor.getBoat().setSailId(newSailId); competitor.getTeam().setNationality(newNationality); competitor.getTeam().setImage(newTeamImageUri); @@ -229,10 +230,11 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab if (competitorDTO == null) { final Nationality nationality = c.getTeam().getNationality(); CountryCode countryCode = nationality == null ? null : nationality.getCountryCode(); - competitorDTO = new CompetitorDTOImpl(c.getName(), c.getColor(), countryCode == null ? "" + competitorDTO = new CompetitorDTOImpl(c.getName(), c.getColor(), c.getEmail(), countryCode == null ? "" : countryCode.getTwoLetterISOCode(), countryCode == null ? "" - : countryCode.getThreeLetterIOCCode(), countryCode == null ? "" : countryCode.getName(), c - .getBoat().getSailID(), c.getId().toString(), new BoatClassDTO(c.getBoat().getBoatClass() + : countryCode.getThreeLetterIOCCode(), countryCode == null ? "" : countryCode.getName(), + c.getBoat().getSailID(), c.getId().toString(), c.getTeam().getImage() == null ? null : c.getTeam().getImage().toString(), + new BoatClassDTO(c.getBoat().getBoatClass() .getName(), c.getBoat().getBoatClass().getDisplayName(), c.getBoat().getBoatClass().getHullLength().getMeters())); weakCompetitorDTOCache.put(c, competitorDTO); } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java index 7f4d7c96c96..673737f2f5b 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java @@ -124,7 +124,7 @@ public class DomainFactoryImpl implements DomainFactory { teamMembers.add(person); } DynamicTeam team = new TeamImpl(competitor.getName(), teamMembers, /* coach */ null); - result = competitorStore.getOrCreateCompetitor(competitor.getID(), competitor.getName(), null /*displayColor*/, team, boat); + result = competitorStore.getOrCreateCompetitor(competitor.getID(), competitor.getName(), null /*displayColor*/, null /*email*/, team, boat); } return result; } @@ -140,7 +140,7 @@ public class DomainFactoryImpl implements DomainFactory { } DynamicTeam team = new TeamImpl(competitor.getName(), teamMembers, /* coach */ null); result = baseDomainFactory.getCompetitorStore().getOrCreateCompetitor(getCompetitorID(competitor.getBoatID(), raceId, boatClass), - competitor.getName(), null /*displayColor*/, team, boat); + competitor.getName(), null /*displayColor*/, null /*email*/, team, boat); return result; } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/LeaderboardDTODiffingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/LeaderboardDTODiffingTest.java index dd915231da5..8127d756a5e 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/LeaderboardDTODiffingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/LeaderboardDTODiffingTest.java @@ -232,8 +232,8 @@ public class LeaderboardDTODiffingTest { @Test public void testCompetitorListChange() { newVersion.competitors = new ArrayList(newVersion.competitors); // clone competitor list so it's not identical to that of previous version - CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", Color.RED, "DE", "GER", "Germany", "GER 1234", "912p09871203987", - new BoatClassDTO("505", 5.05)); + CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", Color.RED, "someone@nobody.de", "DE", "GER", "Germany", "GER 1234", "912p09871203987", + /* imageURL */ null, new BoatClassDTO("505", 5.05)); newVersion.competitors.add(13, somebodyNew); // insert a competitor; this should mess up all others' indexes; check if this works CompetitorDTO wolfgang = getPreviousCompetitorByName("HUNGER +JESS"); newVersion.competitors.remove(wolfgang); @@ -252,8 +252,8 @@ public class LeaderboardDTODiffingTest { Util.addAll(newVersion.getSuppressedCompetitors(), newSuppressedCompetitors); newVersion.setSuppressedCompetitors(newSuppressedCompetitors); newVersion.competitors = new ArrayList(newVersion.competitors); // clone competitor list so it's not identical to that of previous version - CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", Color.RED, "DE", "GER", "Germany", "GER 1234", "912p09871203987", - new BoatClassDTO("505", 5.05)); + CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", Color.RED, "someone@nobody.de", "DE", "GER", "Germany", "GER 1234", "912p09871203987", + /* imageURL */ null, new BoatClassDTO("505", 5.05)); newVersion.setSuppressed(newVersion.competitors.get(13), true); // suppress an existing competitor; compaction should reduce this to a single number only newVersion.setSuppressed(somebodyNew, true); // check that mixed mode with existing and new competitors works as well List newSuppressedCompetitorsBeforeStripping = new ArrayList(); @@ -274,8 +274,8 @@ public class LeaderboardDTODiffingTest { @Test public void testDisplayNameChange() { newVersion.competitors = new ArrayList(newVersion.competitors); // clone competitor list so it's not identical to that of previous version - CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", Color.RED, "DE", "GER", "Germany", "GER 1234", "912p09871203987", - new BoatClassDTO("505", 5.05)); + CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", Color.RED, "someone@nobody.de", "DE", "GER", "Germany", "GER 1234", "912p09871203987", + /* imageURL */ null, new BoatClassDTO("505", 5.05)); newVersion.competitors.add(somebodyNew); newVersion.competitorDisplayNames = new HashMap(newVersion.competitorDisplayNames); newVersion.competitorDisplayNames.put(newVersion.competitors.get(13), "Humba"); @@ -303,8 +303,8 @@ public class LeaderboardDTODiffingTest { List newOrdering = new ArrayList(newVersion.getCompetitorsFromBestToWorst(r9)); newVersion.setCompetitorsFromBestToWorst(r9, newOrdering); newVersion.competitors = new ArrayList(newVersion.competitors); // clone competitor list so it's not identical to that of previous version - CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", Color.RED, "DE", "GER", "Germany", "GER 1234", "912p09871203987", - new BoatClassDTO("505", 5.05)); + CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", Color.RED, "someone@nobody.de", "DE", "GER", "Germany", "GER 1234", "912p09871203987", + /* imageURL */ null, new BoatClassDTO("505", 5.05)); newVersion.competitors.add(somebodyNew); newOrdering.add(somebodyNew); CompetitorDTO formerRank13 = newOrdering.remove(13); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractLeaderboardTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractLeaderboardTest.java index 9870f58ac90..65e0817f4ee 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractLeaderboardTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractLeaderboardTest.java @@ -15,16 +15,16 @@ import com.sap.sse.common.Color; public abstract class AbstractLeaderboardTest { public static CompetitorImpl createCompetitor(String competitorName) { - return new CompetitorImpl(competitorName, competitorName, Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl(competitorName, new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is famous "+competitorName)), - new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), - /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat", + return new CompetitorImpl(competitorName, competitorName, Color.RED, null, new TeamImpl("STG", Collections.singleton( + new PersonImpl(competitorName, new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is famous "+competitorName)), + new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), + /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), /* sailID */ null)); } public static Competitor createCompetitor(String competitorName, CompetitorFactory competitorFactory) { - return competitorFactory.getOrCreateCompetitor(competitorName, competitorName, Color.RED, new TeamImpl("STG", Collections.singleton( + return competitorFactory.getOrCreateCompetitor(competitorName, competitorName, Color.RED, "someone@nobody.de", new TeamImpl("STG", Collections.singleton( new PersonImpl(competitorName, new NationalityImpl("GER"), /* dateOfBirth */ null, "This is famous "+competitorName)), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), @@ -33,11 +33,11 @@ public abstract class AbstractLeaderboardTest { } public static CompetitorImpl createCompetitor(String competitorName, Serializable id) { - return new CompetitorImpl(id, competitorName, Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl(competitorName, new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is famous "+competitorName)), - new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), - /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat", + return new CompetitorImpl(id, competitorName, Color.RED, null, new TeamImpl("STG", Collections.singleton( + new PersonImpl(competitorName, new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is famous "+competitorName)), + new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), + /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), /* sailID */ null)); } } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorListenerTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorListenerTest.java index bdef38602ad..ca8acdfaa70 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorListenerTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorListenerTest.java @@ -94,6 +94,11 @@ public class CompetitorListenerTest extends AbstractSerializationTest { CompetitorListenerTest.this.oldColor = oldColor; CompetitorListenerTest.this.newColor = newColor; } + + @Override + public void emailChanged(String oldEmail, String newEmail) { + //TODO + } }; competitor.addCompetitorChangeListener(listener); } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardCourseChangeTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardCourseChangeTest.java index c6e4e4711ab..b05c4afee5e 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardCourseChangeTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardCourseChangeTest.java @@ -157,8 +157,8 @@ public class LeaderboardCourseChangeTest { Set competitors = new HashSet<>(); DynamicBoat mockedBoat = mock(DynamicBoat.class); when(mockedBoat.getBoatClass()).thenReturn(boatClass); - competitors.add(new CompetitorImpl(UUID.randomUUID(), "TestCompetitor", Color.BLACK, mock(DynamicTeam.class), - mockedBoat)); + competitors.add(new CompetitorImpl(UUID.randomUUID(), "TestCompetitor", Color.BLACK, null, + mock(DynamicTeam.class), mockedBoat)); return competitors; } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OfflineSerializationTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OfflineSerializationTest.java index 1e34a56762c..f6d164910e6 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OfflineSerializationTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OfflineSerializationTest.java @@ -188,11 +188,11 @@ public class OfflineSerializationTest extends AbstractSerializationTest { DomainFactory senderDomainFactory = new DomainFactoryImpl(); DomainFactory receiverDomainFactory = new DomainFactoryImpl(); String competitorName = "Tina Maximiliane Lutz"; - Competitor sendersCompetitor1 = new CompetitorImpl(123, competitorName, Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl(competitorName, senderDomainFactory.getOrCreateNationality("GER"), - /* dateOfBirth */ null, "This is famous "+competitorName)), - new PersonImpl("Rigo van Maas", senderDomainFactory.getOrCreateNationality("GER"), - /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat", + Competitor sendersCompetitor1 = new CompetitorImpl(123, competitorName, Color.RED, null, new TeamImpl("STG", Collections.singleton( + new PersonImpl(competitorName, senderDomainFactory.getOrCreateNationality("GER"), + /* dateOfBirth */ null, "This is famous "+competitorName)), + new PersonImpl("Rigo van Maas", senderDomainFactory.getOrCreateNationality("GER"), + /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat", senderDomainFactory.getOrCreateBoatClass("470", /* typicallyStartsUpwind */ true), "GER 61")); Competitor receiversCompetitor1 = cloneBySerialization(sendersCompetitor1, receiverDomainFactory); Competitor receiversSecondCopyOfCompetitor1 = cloneBySerialization(sendersCompetitor1, receiverDomainFactory); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/StoredTrackBasedTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/StoredTrackBasedTest.java index 9b5bc2dacf6..1ca4fa9f623 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/StoredTrackBasedTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/StoredTrackBasedTest.java @@ -117,7 +117,7 @@ public abstract class StoredTrackBasedTest extends TrackBasedTest { for (String competitorName : getCompetitorNamesOfStoredTracks(KIELER_WOCHE)) { DynamicPerson p = new PersonImpl(competitorName, /* nationality */ null, /* dateOfBirth */ null, /* description */ null); DynamicTeam t = new TeamImpl(competitorName, Collections.singleton(p), /* coach */ null); - Competitor c = new CompetitorImpl(competitorName, competitorName, Color.RED, t, new BoatImpl(competitorName, + Competitor c = new CompetitorImpl(competitorName, competitorName, Color.RED, null, t, new BoatImpl(competitorName, new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); DynamicGPSFixTrack track = readTrack(c, KIELER_WOCHE); if (track != null) { diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TackTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TackTest.java index 414ea94a80f..fc0c6037871 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TackTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TackTest.java @@ -34,11 +34,11 @@ public class TackTest extends StoredTrackBasedTestWithTrackedRace { @Override @Before public void setUp() { - competitor = new CompetitorImpl(123, "Wolfgang Hunger", Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl("Wolfgang Hunger", new NationalityImpl("GER"), - /* dateOfBirth */null, "This is famous Wolfgang Hunger")), new PersonImpl("Rigo van Maas", - new NationalityImpl("NED"), - /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl("Wolfgang Hunger's boat", + competitor = new CompetitorImpl(123, "Wolfgang Hunger", Color.RED, null, new TeamImpl("STG", Collections.singleton( + new PersonImpl("Wolfgang Hunger", new NationalityImpl("GER"), + /* dateOfBirth */null, "This is famous Wolfgang Hunger")), new PersonImpl("Rigo van Maas", + new NationalityImpl("NED"), + /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl("Wolfgang Hunger's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); setTrackedRace(createTestTrackedRace("Kieler Woche", "505 Race 2", "505", Collections.singleton(competitor), new MillisecondsTimePoint(new GregorianCalendar(2011, 05, 23).getTime()))); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java index 99d8ab7bfe9..faeaca23b4e 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java @@ -60,11 +60,11 @@ public abstract class TrackBasedTest { } public static CompetitorImpl createCompetitor(String competitorName) { - return new CompetitorImpl(UUID.randomUUID(), competitorName, Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl(competitorName, new NationalityImpl("GER"), - /* dateOfBirth */null, "This is famous " + competitorName)), new PersonImpl("Rigo van Maas", - new NationalityImpl("NED"), - /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat", + return new CompetitorImpl(UUID.randomUUID(), competitorName, Color.RED, null, new TeamImpl("STG", Collections.singleton( + new PersonImpl(competitorName, new NationalityImpl("GER"), + /* dateOfBirth */null, "This is famous " + competitorName)), new PersonImpl("Rigo van Maas", + new NationalityImpl("NED"), + /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat", new BoatClassImpl("505", /* typicallyStartsUpwind */true), null)); } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java index 73397e43197..64368e6e02e 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java @@ -270,11 +270,11 @@ public class WindTest { Waypoint w1 = domainFactory.createWaypoint(startFinish, /*passingInstruction*/ null); Waypoint w2 = domainFactory.createWaypoint(top, /*passingInstruction*/ null); Waypoint w3 = domainFactory.createWaypoint(startFinish, /*passingInstruction*/ null); - Competitor competitor = new CompetitorImpl(123, "Test Competitor", Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl("Test Competitor", new NationalityImpl("GER"), - /* dateOfBirth */null, "This is famous " + "Test Competitor")), new PersonImpl("Rigo van Maas", - new NationalityImpl("NED"), - /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl("Test Competitor" + "'s boat", + Competitor competitor = new CompetitorImpl(123, "Test Competitor", Color.RED, null, new TeamImpl("STG", Collections.singleton( + new PersonImpl("Test Competitor", new NationalityImpl("GER"), + /* dateOfBirth */null, "This is famous " + "Test Competitor")), new PersonImpl("Rigo van Maas", + new NationalityImpl("NED"), + /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl("Test Competitor" + "'s boat", new BoatClassImpl("505", /* typicallyStartsUpwind */true), null)); final BoatClass boatClass = domainFactory.getOrCreateBoatClass("ESS40"); DynamicTrackedRace trackedRace = new DynamicTrackedRaceImpl(new DynamicTrackedRegattaImpl( diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMockedRaceMarkPassingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMockedRaceMarkPassingTest.java index 4e636675bbd..30ce75475d8 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMockedRaceMarkPassingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMockedRaceMarkPassingTest.java @@ -45,9 +45,9 @@ import com.sap.sse.common.Util; import com.sap.sse.common.impl.MillisecondsTimePoint; public class AbstractMockedRaceMarkPassingTest { - protected Competitor bob = new CompetitorImpl("Bob", "Bob", null, null, null); - protected Competitor joe = new CompetitorImpl("Joe", "Joe", null, null, null); - protected Competitor mike = new CompetitorImpl("Mike", "Mike", null, null, null); + protected Competitor bob = new CompetitorImpl("Bob", "Bob", null, null, null, null); + protected Competitor joe = new CompetitorImpl("Joe", "Joe", null, null, null, null); + protected Competitor mike = new CompetitorImpl("Mike", "Mike", null, null, null, null); protected Mark m = new MarkImpl("black"); protected Mark m2 = new MarkImpl("white 1"); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java index 817943466eb..debb3bdcd57 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java @@ -13,6 +13,7 @@ import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.CourseBase; +import com.sap.sailing.domain.base.EventFetcher; import com.sap.sailing.domain.base.Leg; import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.RaceColumnListener; @@ -290,7 +291,7 @@ public class MockedTrackedRace implements DynamicTrackedRace { } @Override - public Iterable getCompetitors() { + public Iterable getAllCompetitors() { return null; } @@ -430,6 +431,10 @@ public class MockedTrackedRace implements DynamicTrackedRace { @Override public void removeListener(RegattaLikeListener listener) { } + + @Override + public void adjustEventToRegattaAssociation(EventFetcher eventFetcher) { + } }; } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java index 289ecceb33e..650902ae32a 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java @@ -259,7 +259,7 @@ public class DomainFactoryImpl implements DomainFactory { } DynamicTeam team = getOrCreateTeam(name, nationality, competitorId); DynamicBoat boat = new BoatImpl(shortName, boatClass, shortName); - result = competitorStore.getOrCreateCompetitor(competitorId, name, null /*displayColor*/, team, boat); + result = competitorStore.getOrCreateCompetitor(competitorId, name, null /*displayColor*/, null /*email*/, team, boat); } return result; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/EventFetcher.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/EventFetcher.java new file mode 100755 index 00000000000..98209eaeef1 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/EventFetcher.java @@ -0,0 +1,5 @@ +package com.sap.sailing.domain.base; + +public interface EventFetcher { + Iterable getAllEvents(); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java index 0667d9ade2a..1dfb3d18d64 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java @@ -103,7 +103,7 @@ public interface Regatta extends Named, WithID, IsRegattaLike { BoatClass getBoatClass(); - Iterable getCompetitors(); + Iterable getAllCompetitors(); /** * Will remove the series from this regatta. Will also call {@link RaceColumn#removeRaceIdentifier(Fleet)} to make @@ -168,4 +168,12 @@ public interface Regatta extends Named, WithID, IsRegattaLike { * Updates the flag deciding whether to use start time inference. See {@link #useStartTimeInference()}. */ void setUseStartTimeInference(boolean useStartTimeInference); + + /** + * {@link Event} manages an association to its {@link Event#getRegattas() regattas}. When something on the + * regatta changes (in particular, the implicit link to an event through an event's {@link CourseArea} that + * is assigned as this regatta's {@link #setDefaultCourseArea(CourseArea) default course area}), the + * opposite end of the association needs to be maintained on the event's side. + */ + void adjustEventToRegattaAssociation(EventFetcher eventFetcher); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DynamicCompetitorStore.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DynamicCompetitorStore.java index aafc1d26917..3dffba3a7f5 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DynamicCompetitorStore.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DynamicCompetitorStore.java @@ -10,7 +10,7 @@ public interface DynamicCompetitorStore extends CompetitorStore { DynamicCompetitor getExistingCompetitorById(Serializable competitorId); @Override - DynamicCompetitor getOrCreateCompetitor(Serializable competitorId, String name, Color displayColor, DynamicTeam team, DynamicBoat boat); + DynamicCompetitor getOrCreateCompetitor(Serializable competitorId, String name, Color displayColor, String email, DynamicTeam team, DynamicBoat boat); @Override Iterable getCompetitors(); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java index 8dd579aaac4..5170bb3b1b5 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java @@ -12,9 +12,12 @@ import java.util.logging.Logger; import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; +import com.sap.sailing.domain.abstractlog.shared.analyzing.RegisteredCompetitorsAnalyzer; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseArea; +import com.sap.sailing.domain.base.Event; +import com.sap.sailing.domain.base.EventFetcher; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.RaceColumnInSeries; @@ -297,13 +300,26 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene } @Override - public Iterable getCompetitors() { + public Iterable getAllCompetitors() { Set result = new HashSet(); for (RaceDefinition race : getAllRaces()) { for (Competitor c : race.getCompetitors()) { result.add(c); } } + for (Series series : getSeries()) { + for (RaceColumn rc : series.getRaceColumns()) { + for (Fleet fleet : rc.getFleets()) { + TrackedRace trackedRace = rc.getTrackedRace(fleet); + if (trackedRace != null) { + Util.addAll(trackedRace.getRace().getCompetitors(), result); + } + } + } + } + //consider {@link RegattaLog} + Set viaLog = new RegisteredCompetitorsAnalyzer<>(regattaLikeHelper.getRegattaLog()).analyze(); + result.addAll(viaLog); return result; } @@ -551,4 +567,17 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene public void removeListener(RegattaLikeListener listener) { regattaLikeHelper.removeListener(listener); } + + public void adjustEventToRegattaAssociation(EventFetcher eventFetcher) { + CourseArea defaultCourseArea = getDefaultCourseArea(); + for (Event event : eventFetcher.getAllEvents()) { + event.removeRegatta(this); + for (CourseArea courseArea : event.getVenue().getCourseAreas()) { + if (defaultCourseArea != null && courseArea.getId().equals(defaultCourseArea)) { + event.addRegatta(this); + } + } + } + + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java index d2f0bcc4713..2f7db478424 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java @@ -88,7 +88,9 @@ public interface Leaderboard extends LeaderboardBase { * in a regular view of the leaderboard. Editors for leaderboards shall, though, also display the suppressed competitors * together with a visual indication showing which competitors are currently suppressed. The "suppressed-state" of a * competitor can change over the life cycle of a leaderboard. A typical use case for suppressing a competitor is - * removing one-time entries from a series (meta-)leaderboard or suppressing a camera boat in scoring. + * removing one-time entries from a series (meta-)leaderboard or suppressing a camera boat in scoring.

    + * + * @return all competitors in this leaderboard, including the suppressed ones. */ Iterable getAllCompetitors(); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardCacheManager.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardCacheManager.java index 481d63b3cc3..6dfe6c5521a 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardCacheManager.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardCacheManager.java @@ -116,6 +116,11 @@ public class LeaderboardCacheManager { competitor.removeCompetitorChangeListener(this); } } + + @Override + public void emailChanged(String oldEmail, String newEmail) { + //ignore (email not shown in leaderboard) + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractLeaderboardImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractLeaderboardImpl.java index f68c204399f..1a447be2b5f 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractLeaderboardImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractLeaderboardImpl.java @@ -70,6 +70,10 @@ public abstract class AbstractLeaderboardImpl extends AbstractSimpleLeaderboardI return Collections.unmodifiableSet(trackedRaces); } + /** + * This default implementation collects all competitors by visiting all {@link TrackedRace}s associated with this + * leaderboard's columns (see {@link #getTrackedRaces()}). + */ @Override public Iterable getAllCompetitors() { if (allCompetitorsCache == null) { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/FlexibleLeaderboardImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/FlexibleLeaderboardImpl.java index aee04ead6da..989e468e8b9 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/FlexibleLeaderboardImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/FlexibleLeaderboardImpl.java @@ -6,10 +6,14 @@ import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.logging.Logger; import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; +import com.sap.sailing.domain.abstractlog.shared.analyzing.RegisteredCompetitorsAnalyzer; +import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.RaceColumn; @@ -275,4 +279,16 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements public void removeListener(RegattaLikeListener listener) { regattaLikeHelper.removeListener(listener); } + + @Override + public Iterable getAllCompetitors() { + Set result = new HashSet<>(); + for (Competitor c : super.getAllCompetitors()) { + result.add(c); + } + //consider {@link RegattaLog} + Set viaLog = new RegisteredCompetitorsAnalyzer<>(regattaLikeHelper.getRegattaLog()).analyze(); + result.addAll(viaLog); + return result; + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java index 0301eb7e8a9..a6df854049f 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java @@ -3,11 +3,14 @@ package com.sap.sailing.domain.leaderboard.impl; import java.util.ArrayList; import java.util.List; +import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; +import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.RaceColumnInSeries; import com.sap.sailing.domain.base.RaceColumnListener; +import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.Series; import com.sap.sailing.domain.base.impl.RaceColumnInSeriesImpl; @@ -96,6 +99,17 @@ public class RegattaLeaderboardImpl extends AbstractLeaderboardImpl implements R } } + /** + * Delegates to {@link Regatta#getAllCompetitors()} which is expected to deliver all competitors from this + * leaderboard's regatta which includes those belonging to {@link RaceDefinition races} + * {@link Regatta#getAllRaces() belonging to the regatta} as well as competitors listed on the regatta's + * {@link RegattaLog}. + */ + @Override + public Iterable getAllCompetitors() { + return regatta.getAllCompetitors(); + } + @Override public IsRegattaLike getRegattaLike() { return regatta; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateFinder.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateFinder.java index 75814996290..eceabaef3fd 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateFinder.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateFinder.java @@ -368,8 +368,13 @@ public class CandidateFinder implements AbstractCandidateFinder { boolean result = true; if (passingInstructions.get(w) == PassingInstruction.Port || passingInstructions.get(w) == PassingInstruction.Starboard || passingInstructions.get(w) == PassingInstruction.FixedBearing) { - result = p.crossTrackError(race.getOrCreateTrack(w.getMarks().iterator().next()).getEstimatedPosition(t, true), - race.getCrossingBearing(w, t).add(new DegreeBearingImpl(90))).getMeters() < 0; + final Position estimatedMarkPosition = race.getOrCreateTrack(w.getMarks().iterator().next()).getEstimatedPosition(t, true); + if (estimatedMarkPosition != null) { + result = p.crossTrackError(estimatedMarkPosition, + race.getCrossingBearing(w, t).add(new DegreeBearingImpl(90))).getMeters() < 0; + } else { + // TODO + } } else if (passingInstructions.get(w) == PassingInstruction.Gate) { // TODO } else if (passingInstructions.get(w) == PassingInstruction.Offset) { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DummyMarkPassingWithTimePointOnly.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DummyMarkPassingWithTimePointOnly.java index 41bfb97db8b..a598b10a738 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DummyMarkPassingWithTimePointOnly.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DummyMarkPassingWithTimePointOnly.java @@ -72,6 +72,16 @@ public class DummyMarkPassingWithTimePointOnly implements MarkPassing { @Override public void removeCompetitorChangeListener(CompetitorChangeListener listener) { } + + @Override + public String getEmail() { + return null; + } + + @Override + public boolean hasEmail() { + return false; + } }; } } diff --git a/java/com.sap.sailing.feature/feature.xml b/java/com.sap.sailing.feature/feature.xml index b58a6a25057..00b023d577a 100755 --- a/java/com.sap.sailing.feature/feature.xml +++ b/java/com.sap.sailing.feature/feature.xml @@ -9,392 +9,392 @@ The SAP Sailing Server. Provides connectivity to a TracTrac live GPS feed as well as a live feed from an Expedition system. -Computes leaderboard information for sailing races and offers RESTful APIs to all of this. - - - - [Enter Copyright Description here.] - - - - [Enter License Description here.] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Computes leaderboard information for sailing races and offers RESTful APIs to all of this. + + + + [Enter Copyright Description here.] + + + + [Enter License Description here.] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java index bf1191a0747..15bf4fec23f 100755 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java @@ -11,6 +11,7 @@ import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.CourseBase; +import com.sap.sailing.domain.base.EventFetcher; import com.sap.sailing.domain.base.Leg; import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.RaceColumnListener; @@ -335,7 +336,7 @@ public class MockedTrackedRace implements DynamicTrackedRace { } @Override - public Iterable getCompetitors() { + public Iterable getAllCompetitors() { // TODO Auto-generated method stub return null; } @@ -517,6 +518,12 @@ public class MockedTrackedRace implements DynamicTrackedRace { // TODO Auto-generated method stub } + + @Override + public void adjustEventToRegattaAssociation(EventFetcher eventFetcher) { + // TODO Auto-generated method stub + + } }; } diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java index e9311746a13..9dd9fc9cd5f 100755 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java @@ -69,7 +69,7 @@ public class PolarSheetGenerationServiceTest { double[] levels = { 4., 6., 8., 10., 12., 14., 16., 20., 25., 30. }; WindSpeedSteppingWithMaxDistance windStepping = new WindSpeedSteppingWithMaxDistance(levels, 2.0); PolarSheetGenerationSettings settings = new PolarSheetGenerationSettingsImpl(1, 0, 1, 20, 0, false, true, 5, - 0.05, false, windStepping, false); + 0.05, false, windStepping, false, 0); List idList = new ArrayList(); idList.add(new RegattaNameAndRaceName("IrgendeineRegatta", "IrgendeinRennen")); @@ -163,8 +163,8 @@ public class PolarSheetGenerationServiceTest { waypoints.add(new WaypointImpl(null)); waypoints.add(new WaypointImpl(null)); ArrayList competitors = new ArrayList(); - Competitor competitor = new CompetitorImpl(UUID.randomUUID(), "Hans Frantz", Color.RED, new TeamImpl("SAP", null, null), - new BoatImpl("Schnelle Forelle", forelle, "GER000")); + Competitor competitor = new CompetitorImpl(UUID.randomUUID(), "Hans Frantz", Color.RED, null, + new TeamImpl("SAP", null, null), new BoatImpl("Schnelle Forelle", forelle, "GER000")); competitors.add(competitor); RaceDefinition race = new RaceDefinitionImpl("Forelle1", new CourseImpl("ForelleCourse", waypoints), forelle, competitors); return race; diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SailingServiceImplMock.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SailingServiceImplMock.java index 31bba094c52..7803176ca7c 100755 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SailingServiceImplMock.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SailingServiceImplMock.java @@ -1,8 +1,5 @@ package com.sap.sailing.gwt.ui.test; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - import java.io.IOException; import java.net.UnknownHostException; import java.text.ParseException; @@ -11,9 +8,6 @@ import java.util.List; import javax.xml.bind.JAXBException; -import org.osgi.framework.BundleContext; -import org.osgi.util.tracker.ServiceTracker; - import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.DomainFactory; import com.sap.sailing.domain.common.RegattaIdentifier; @@ -27,7 +21,6 @@ import com.sap.sailing.domain.swisstimingadapter.SwissTimingAdapter; import com.sap.sailing.domain.swisstimingadapter.SwissTimingAdapterFactory; import com.sap.sailing.domain.swisstimingadapter.SwissTimingFactory; import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayService; -import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayServiceFactory; import com.sap.sailing.domain.tracking.RaceHandle; import com.sap.sailing.domain.tracking.TrackerManager; import com.sap.sailing.domain.tractracadapter.TracTracAdapterFactory; @@ -43,72 +36,37 @@ import com.sap.sse.replication.impl.ReplicationServiceImpl; public class SailingServiceImplMock extends SailingServiceImpl { private static final long serialVersionUID = 8564037671550730455L; - private RacingEventService service; - + + private final RacingEventService service; + private ReplicationService replicationService; + private final ScoreCorrectionProvider scoreCorrectionProvider = null; + private final TracTracAdapterFactory tracTracAdapterFactory = TracTracAdapterFactory.INSTANCE; + private final SwissTimingAdapterFactory swissTimingAdapterFactory; + private final SwissTimingReplayService swissTimingReplayService = null; + private final RaceLogTrackingAdapterFactory raceLogTrackingAdapterFactory = RaceLogTrackingAdapterFactory.INSTANCE; + public SailingServiceImplMock() { super(); service = new RacingEventServiceImpl(true, new MockSmartphoneImeiServiceFinderFactory()); - } - - @Override - protected ServiceTracker createAndOpenRacingEventServiceTracker( - BundleContext context) { - @SuppressWarnings("unchecked") - ServiceTracker result = mock(ServiceTracker.class); - when(result.getService()).thenReturn(new RacingEventServiceImpl()); - return result; - } - - @Override - protected ServiceTracker createAndOpenReplicationServiceTracker( - BundleContext context) { - @SuppressWarnings("unchecked") - ServiceTracker result = mock(ServiceTracker.class); try { - final RacingEventService replicable = new RacingEventServiceImpl(); - final ReplicationServiceImpl replicationService = - new ReplicationServiceImpl("test exchange", "localhost", 0, - new ReplicationInstancesManager(), new AbstractReplicablesProvider() { - @Override - public Iterable> getReplicables() { - return Collections.>singleton(replicable); - } - }); - when(result.getService()).thenReturn(replicationService); + replicationService = new ReplicationServiceImpl("test exchange", "localhost", 0, + new ReplicationInstancesManager(), new AbstractReplicablesProvider() { + @Override + public Iterable> getReplicables() { + return Collections.> singleton(service); + } + }); } catch (IOException e) { - throw new RuntimeException(); + e.printStackTrace(); } - return result; - } - - @Override - protected ServiceTracker createAndOpenScoreCorrectionProviderServiceTracker( - BundleContext bundleContext) { - return null; - } - - @Override - protected ServiceTracker createAndOpenTracTracAdapterTracker( - BundleContext context) { - @SuppressWarnings("unchecked") - ServiceTracker result = mock(ServiceTracker.class); - when(result.getService()).thenReturn(TracTracAdapterFactory.INSTANCE); - return result; - } - - @Override - protected ServiceTracker createAndOpenSwissTimingAdapterTracker( - BundleContext context) { - @SuppressWarnings("unchecked") - ServiceTracker result = mock(ServiceTracker.class); - when(result.getService()).thenReturn(new SwissTimingAdapterFactory() { + swissTimingAdapterFactory = new SwissTimingAdapterFactory() { @Override public SwissTimingAdapter getOrCreateSwissTimingAdapter(DomainFactory baseDomainFactory) { return new SwissTimingAdapter() { @Override public List getSwissTimingRaceRecords( - String hostname, int port) throws InterruptedException, - UnknownHostException, IOException, ParseException { + String hostname, int port) throws InterruptedException, UnknownHostException, IOException, + ParseException { // TODO Auto-generated method stub return null; } @@ -148,45 +106,43 @@ public class SailingServiceImplMock extends SailingServiceImpl { } }; } - }); - return result; + }; + } + + public ScoreCorrectionProvider getScoreCorrectionProvider() { + return scoreCorrectionProvider; } @Override - protected ServiceTracker createAndOpenSwissTimingReplayServiceTracker( - BundleContext context) { - @SuppressWarnings("unchecked") - ServiceTracker result = mock(ServiceTracker.class); - final SwissTimingReplayServiceFactory swissTimingReplayService = new SwissTimingReplayServiceFactory() { - @Override - public SwissTimingReplayService createSwissTimingReplayService( - com.sap.sailing.domain.swisstimingadapter.DomainFactory domainFactory) { - // TODO Auto-generated method stub - return null; - } - }; - when(result.getService()).thenReturn(swissTimingReplayService); - return result; + protected TracTracAdapterFactory getTracTracAdapterFactory() { + return tracTracAdapterFactory; + } + + @Override + protected SwissTimingAdapterFactory getSwissTimingAdapterFactory() { + return swissTimingAdapterFactory; + } + + @Override + protected SwissTimingReplayService getSwissTimingReplayService() { + return swissTimingReplayService; + } + + @Override + protected RaceLogTrackingAdapterFactory getRaceLogTrackingAdapterFactory() { + return raceLogTrackingAdapterFactory; } @Override - protected ServiceTracker createAndOpenRaceLogTrackingAdapterTracker( - BundleContext context) { - @SuppressWarnings("unchecked") - ServiceTracker result = mock(ServiceTracker.class); - RaceLogTrackingAdapterFactory factory = RaceLogTrackingAdapterFactory.INSTANCE; - when(result.getService()).thenReturn(factory); - return result; + protected ReplicationService getReplicationService() { + return replicationService; } @Override protected RacingEventService getService() { - if (service == null) { - service = new RacingEventServiceImpl(); - } return service; } - + public RacingEventService getRacingEventService() { return getService(); } diff --git a/java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs b/java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs index ba74941703a..a44f1dda0ca 100755 --- a/java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs +++ b/java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs @@ -1,4 +1,4 @@ eclipse.preferences.version=1 entryPointModules= filesCopiedToWebInfLib=gwt-servlet.jar -gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDQgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0VdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC5Ib21lPC9lbnRyeS1wb2ludC1tb2R1bGU+PC9nd3QtY29tcGlsZS1zZXR0aW5ncz4\= +gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDQgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0VdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC5BdXRvUGxheTwvZW50cnktcG9pbnQtbW9kdWxlPjwvZ3d0LWNvbXBpbGUtc2V0dGluZ3M+ diff --git a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF index 0f82a9aec59..26eb1466b06 100644 --- a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF @@ -45,7 +45,8 @@ Require-Bundle: com.sap.sailing.domain, com.sap.sse.operationaltransformation, com.sap.sse, com.sap.sse.security.common, - com.sap.sse.filestorage + com.sap.sse.filestorage, + org.apache.shiro.core;bundle-version="1.2.2" Bundle-Activator: com.sap.sailing.gwt.ui.server.Activator Bundle-ActivationPolicy: lazy Import-Package: com.sap.sailing.polars, diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling (Run-Mode only).launch b/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling (Run-Mode only).launch index 7c55b6a1b87..ad65be7f6f2 100644 --- a/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling (Run-Mode only).launch +++ b/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling (Run-Mode only).launch @@ -1,60 +1,60 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch b/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch index b0332ffab3a..0423a6e759c 100755 --- a/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch +++ b/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch @@ -1,75 +1,76 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT.launch b/java/com.sap.sailing.gwt.ui/SailingGWT.launch index cedd1dcfc01..f02353541f2 100755 --- a/java/com.sap.sailing.gwt.ui/SailingGWT.launch +++ b/java/com.sap.sailing.gwt.ui/SailingGWT.launch @@ -1,73 +1,74 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages.java index c201c80ce34..e11db038d23 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages.java @@ -6,6 +6,7 @@ import com.google.gwt.i18n.client.Messages; public interface TextMessages extends Messages { public static final TextMessages INSTANCE = GWT.create(TextMessages.class); + String chartSelectionHint(); String chooseALanguage(); String home(); String events(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages.properties index 6a50d7fa2ce..8ad020fd7fd 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages.properties @@ -1,3 +1,4 @@ +chartSelectionHint=Hint: You can also use the leaderboard (on the LEADERBOARD tab) to select the competitors displayed in the chart. chooseALanguage=Choose a language events=Events home=Home diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_de.properties index 3afbaca10ad..55b7a72c686 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_de.properties @@ -1,3 +1,4 @@ +chartSelectionHint=Hinweis: Sie können auch Ergebnisstabelle (im Menü ERGEBNISSE) nutzen, um die im Diagramm angezeigten Teilnehmer auszuwählen. chooseALanguage=Wähle eine Sprache events=Veranstaltungen home=Startseite diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/CompactEventHeader.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/CompactEventHeader.ui.xml index 4a134f1f2d9..7e3fb498de0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/CompactEventHeader.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/CompactEventHeader.ui.xml @@ -9,7 +9,7 @@

    -
    +
    diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.java index 0ff0c7ee412..bb66418a237 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.java @@ -4,6 +4,8 @@ import java.util.ArrayList; import java.util.List; import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Style.Visibility; import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; @@ -14,9 +16,11 @@ import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.gwt.home.client.place.event.regattaleaderboard.EventRegattaLeaderboardResources; +import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider; import com.sap.sailing.gwt.ui.client.DetailTypeFormatter; import com.sap.sailing.gwt.ui.client.shared.charts.MultiCompetitorLeaderboardChart; import com.sap.sailing.gwt.ui.client.shared.charts.MultiCompetitorLeaderboardChartSettings; +import com.sap.sse.common.Util; public class OldCompetitorCharts extends Composite { private static OldCompetitorChartsUiBinder uiBinder = GWT.create(OldCompetitorChartsUiBinder.class); @@ -26,6 +30,7 @@ public class OldCompetitorCharts extends Composite { @UiField HTMLPanel oldCompetitorChartsPanel; @UiField ListBox chartTypeSelectionListBox; + @UiField DivElement competitorSelectionStateUi; private final List availableDetailsTypes; private MultiCompetitorLeaderboardChart multiCompetitorChart; @@ -79,4 +84,15 @@ public class OldCompetitorCharts extends Composite { } return result; } + + public void updateSelectionState(CompetitorSelectionProvider competitorSelectionProvider) { + int selectedCompetitorsCount = Util.size(competitorSelectionProvider.getSelectedCompetitors()); + if(selectedCompetitorsCount > 0) { + int competitorsCount = Util.size(competitorSelectionProvider.getAllCompetitors()); + competitorSelectionStateUi.getStyle().setVisibility(Visibility.VISIBLE); + competitorSelectionStateUi.setInnerText(selectedCompetitorsCount + "/" + competitorsCount + " competitors selected."); + } else { + competitorSelectionStateUi.getStyle().setVisibility(Visibility.HIDDEN); + } + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.ui.xml index 997e1bc24b5..68d7122a180 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.ui.xml @@ -7,7 +7,7 @@ -
    +

    @@ -15,6 +15,11 @@
    +
    + +
    +
    +
    diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldleaderboard/OldLeaderboard.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldleaderboard/OldLeaderboard.ui.xml index 8f65326ebe2..56f6dc4dbf8 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldleaderboard/OldLeaderboard.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldleaderboard/OldLeaderboard.ui.xml @@ -7,7 +7,7 @@ -
    +

    diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldmultileaderboard/OldMultiLeaderboard.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldmultileaderboard/OldMultiLeaderboard.ui.xml index 5b78dfeda4e..6bf4b5d7afd 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldmultileaderboard/OldMultiLeaderboard.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldmultileaderboard/OldMultiLeaderboard.ui.xml @@ -7,7 +7,7 @@ -
    +

    diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalytics.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalytics.java index f80497147bf..169f4dd0a5b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalytics.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalytics.java @@ -181,6 +181,7 @@ public class RegattaAnalytics extends Composite implements LeaderboardUpdateList setActiveTabPanel(competitorChartsTabPanel, competitorChartsAnchor); DetailType selectedChartDetailType = oldCompetitorChartsComposite.getSelectedChartDetailType(); regattaAnalyticsManager.showCompetitorChart(selectedChartDetailType); + oldCompetitorChartsComposite.updateSelectionState(regattaAnalyticsManager.getCompetitorSelectionProvider()); handleClickEvent(event, competitorChartsNavigation); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalyticsDataManager.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalyticsDataManager.java index 5e7ec9ea299..9b0590d8434 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalyticsDataManager.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalyticsDataManager.java @@ -1,7 +1,13 @@ package com.sap.sailing.gwt.home.client.place.event.regattaanalytics; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.dto.CompetitorDTO; +import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener; import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; import com.sap.sailing.gwt.ui.client.DebugIdHelper; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; @@ -12,6 +18,7 @@ import com.sap.sailing.gwt.ui.client.shared.components.Component; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialog; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.player.Timer; @@ -32,7 +39,8 @@ public class RegattaAnalyticsDataManager { private final UserAgentDetails userAgent; private final SailingServiceAsync sailingService; private final Timer timer; - + private final int MAX_COMPETITORS_IN_CHART = 30; + public RegattaAnalyticsDataManager(final SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, Timer timer, ErrorReporter errorReporter, UserAgentDetails userAgent) { this.competitorSelectionProvider = new CompetitorSelectionModel(/* hasMultiSelection */true); this.sailingService = sailingService; @@ -73,12 +81,32 @@ public class RegattaAnalyticsDataManager { return multiCompetitorChart; } + public CompetitorSelectionModel getCompetitorSelectionProvider() { + return competitorSelectionProvider; + } + public void showCompetitorChart(DetailType chartDetailType) { + // preselect the top N competitors in case there was no selection before and there too many competitors for a chart + int competitorsCount = Util.size(competitorSelectionProvider.getAllCompetitors()); + int selectedCompetitorsCount = Util.size(competitorSelectionProvider.getSelectedCompetitors()); + + if(selectedCompetitorsCount == 0 && competitorsCount > MAX_COMPETITORS_IN_CHART) { + List selectedCompetitors = new ArrayList(); + Iterator allCompetitorsIt = competitorSelectionProvider.getAllCompetitors().iterator(); + int counter = 0; + while(counter < MAX_COMPETITORS_IN_CHART) { + selectedCompetitors.add(allCompetitorsIt.next()); + counter++; + } + competitorSelectionProvider.setSelection(selectedCompetitors, (CompetitorSelectionChangeListener[]) null); + } + MultiCompetitorLeaderboardChart multiCompetitorChart = getMultiCompetitorChart(); MultiCompetitorLeaderboardChartSettings settings = new MultiCompetitorLeaderboardChartSettings(chartDetailType); multiCompetitorChart.updateSettings(settings); multiCompetitorChart.setVisible(true); timer.addTimeListener(multiCompetitorChart); + multiCompetitorChart.clearChart(); multiCompetitorChart.timeChanged(timer.getTime(), null); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaleaderboard/EventRegattaLeaderboard.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaleaderboard/EventRegattaLeaderboard.css index dec9fb3e38a..427b670805c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaleaderboard/EventRegattaLeaderboard.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaleaderboard/EventRegattaLeaderboard.css @@ -38,6 +38,8 @@ text-align: center; } .regattaleaderboard { + margin-left: 5rem; + margin-right: 5rem; font-size: 15px; font-size: 1rem; width: 100%; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/seriesanalytics/EventSeriesAnalytics.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/seriesanalytics/EventSeriesAnalytics.java index 3d48af5335a..be1c4184f59 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/seriesanalytics/EventSeriesAnalytics.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/seriesanalytics/EventSeriesAnalytics.java @@ -212,6 +212,7 @@ public class EventSeriesAnalytics extends Composite implements LeaderboardUpdate setActiveTabPanel(competitorChartsTabPanel, eventSeriesAnalyticsManager.getMultiCompetitorChart(), competitorChartsAnchor); DetailType selectedChartDetailType = oldCompetitorChartsComposite.getSelectedChartDetailType(); eventSeriesAnalyticsManager.showCompetitorChart(selectedChartDetailType); + oldCompetitorChartsComposite.updateSelectionState(eventSeriesAnalyticsManager.getCompetitorSelectionProvider()); handleClickEvent(event, competitorChartsNavigation); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/seriesanalytics/EventSeriesAnalyticsDataManager.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/seriesanalytics/EventSeriesAnalyticsDataManager.java index 52669221b5b..35f0d588f16 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/seriesanalytics/EventSeriesAnalyticsDataManager.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/seriesanalytics/EventSeriesAnalyticsDataManager.java @@ -1,7 +1,13 @@ package com.sap.sailing.gwt.home.client.place.event.seriesanalytics; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.dto.CompetitorDTO; +import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener; import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; import com.sap.sailing.gwt.ui.client.DebugIdHelper; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; @@ -13,6 +19,7 @@ import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialog; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings; import com.sap.sailing.gwt.ui.leaderboard.MultiLeaderboardPanel; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.player.Timer; @@ -36,6 +43,7 @@ public class EventSeriesAnalyticsDataManager { private final UserAgentDetails userAgent; private final SailingServiceAsync sailingService; private final Timer timer; + private final int MAX_COMPETITORS_IN_CHART = 30; public EventSeriesAnalyticsDataManager(final SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, Timer timer, ErrorReporter errorReporter, UserAgentDetails userAgent) { this.competitorSelectionProvider = new CompetitorSelectionModel(/* hasMultiSelection */true); @@ -93,14 +101,34 @@ public class EventSeriesAnalyticsDataManager { } public void showCompetitorChart(DetailType chartDetailType) { + // preselect the top N competitors in case there was no selection before and there too many competitors for a chart + int competitorsCount = Util.size(competitorSelectionProvider.getAllCompetitors()); + int selectedCompetitorsCount = Util.size(competitorSelectionProvider.getSelectedCompetitors()); + + if(selectedCompetitorsCount == 0 && competitorsCount > MAX_COMPETITORS_IN_CHART) { + List selectedCompetitors = new ArrayList(); + Iterator allCompetitorsIt = competitorSelectionProvider.getAllCompetitors().iterator(); + int counter = 0; + while(counter < MAX_COMPETITORS_IN_CHART) { + selectedCompetitors.add(allCompetitorsIt.next()); + counter++; + } + competitorSelectionProvider.setSelection(selectedCompetitors, (CompetitorSelectionChangeListener[]) null); + } + MultiCompetitorLeaderboardChart multiCompetitorChart = getMultiCompetitorChart(); MultiCompetitorLeaderboardChartSettings settings = new MultiCompetitorLeaderboardChartSettings(chartDetailType); multiCompetitorChart.updateSettings(settings); multiCompetitorChart.setVisible(true); timer.addTimeListener(multiCompetitorChart); + multiCompetitorChart.clearChart(); multiCompetitorChart.timeChanged(timer.getTime(), null); } + public CompetitorSelectionModel getCompetitorSelectionProvider() { + return competitorSelectionProvider; + } + public void hideCompetitorChart() { MultiCompetitorLeaderboardChart multiCompetitorChart = getMultiCompetitorChart(); multiCompetitorChart.setVisible(false); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/regatta/RegattaActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/regatta/RegattaActivity.java index 685be39e328..0eb19bac8e6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/regatta/RegattaActivity.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/regatta/RegattaActivity.java @@ -144,6 +144,9 @@ public class RegattaActivity extends AbstractActivity { List maneuverDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_MANEUVER_DETAIL)); List raceDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_RACE_DETAIL)); List overallDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_OVERALL_DETAIL)); + if (overallDetails.isEmpty()) { + overallDetails = Collections.singletonList(DetailType.REGATTA_RANK); + } List legDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_LEG_DETAIL)); List namesOfRacesToShow = getStringListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_RACE_NAME)); boolean showAddedScores = parameterMap.containsKey(LeaderboardUrlSettings.PARAM_SHOW_ADDED_SCORES) ? @@ -153,13 +156,27 @@ public class RegattaActivity extends AbstractActivity { boolean autoExpandPreSelectedRace = parameterMap.containsKey(LeaderboardUrlSettings.PARAM_AUTO_EXPAND_PRESELECTED_RACE) ? Boolean.valueOf(parameterMap.get(LeaderboardUrlSettings.PARAM_AUTO_EXPAND_PRESELECTED_RACE).get(0)) : (namesOfRacesToShow != null && namesOfRacesToShow.size() == 1); + boolean showCompetitorSailIdColumn = true; + boolean showCompetitorFullNameColumn = true; + if (parameterMap.containsKey(LeaderboardUrlSettings.PARAM_SHOW_COMPETITOR_NAME_COLUMNS)) { + String value = parameterMap.get(LeaderboardUrlSettings.PARAM_SHOW_COMPETITOR_NAME_COLUMNS).get(0); + if (value.equals(LeaderboardUrlSettings.COMPETITOR_NAME_COLUMN_FULL_NAME)) { + showCompetitorSailIdColumn = false; + } else if (value.equals(LeaderboardUrlSettings.COMPETITOR_NAME_COLUMN_SAIL_ID)) { + showCompetitorFullNameColumn = false; + } else if (value.trim().equals("")) { + showCompetitorFullNameColumn = false; + showCompetitorSailIdColumn = false; + } + } result = new LeaderboardSettings(maneuverDetails, legDetails, raceDetails, overallDetails, /* namesOfRaceColumnsToShow */ null, namesOfRacesToShow, numberOfLastRacesToShow, autoExpandPreSelectedRace, refreshIntervalMillis, /* sort by column */ (namesOfRacesToShow != null && !namesOfRacesToShow.isEmpty()) ? namesOfRacesToShow.get(0) : null, /* ascending */ true, /* updateUponPlayStateChange */ raceDetails.isEmpty() && legDetails.isEmpty(), - raceColumnSelectionStrategy, showAddedScores, showOverallColumnWithNumberOfRacesSailedPerCompetitor); + raceColumnSelectionStrategy, showAddedScores, showOverallColumnWithNumberOfRacesSailedPerCompetitor, + showCompetitorSailIdColumn, showCompetitorFullNameColumn); } else { final List overallDetails = Collections.singletonList(DetailType.REGATTA_RANK); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/series/SeriesActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/series/SeriesActivity.java index 46649dcbd4c..eeb3714d515 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/series/SeriesActivity.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/series/SeriesActivity.java @@ -145,6 +145,9 @@ public class SeriesActivity extends AbstractActivity { List maneuverDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_MANEUVER_DETAIL)); List raceDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_RACE_DETAIL)); List overallDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_OVERALL_DETAIL)); + if (overallDetails.isEmpty()) { + overallDetails = Collections.singletonList(DetailType.REGATTA_RANK); + } List legDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_LEG_DETAIL)); List namesOfRacesToShow = getStringListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_RACE_NAME)); boolean showAddedScores = parameterMap.containsKey(LeaderboardUrlSettings.PARAM_SHOW_ADDED_SCORES) ? @@ -154,13 +157,27 @@ public class SeriesActivity extends AbstractActivity { boolean autoExpandPreSelectedRace = parameterMap.containsKey(LeaderboardUrlSettings.PARAM_AUTO_EXPAND_PRESELECTED_RACE) ? Boolean.valueOf(parameterMap.get(LeaderboardUrlSettings.PARAM_AUTO_EXPAND_PRESELECTED_RACE).get(0)) : (namesOfRacesToShow != null && namesOfRacesToShow.size() == 1); + boolean showCompetitorSailIdColumn = true; + boolean showCompetitorFullNameColumn = true; + if (parameterMap.containsKey(LeaderboardUrlSettings.PARAM_SHOW_COMPETITOR_NAME_COLUMNS)) { + String value = parameterMap.get(LeaderboardUrlSettings.PARAM_SHOW_COMPETITOR_NAME_COLUMNS).get(0); + if (value.equals(LeaderboardUrlSettings.COMPETITOR_NAME_COLUMN_FULL_NAME)) { + showCompetitorSailIdColumn = false; + } else if (value.equals(LeaderboardUrlSettings.COMPETITOR_NAME_COLUMN_SAIL_ID)) { + showCompetitorFullNameColumn = false; + } else if (value.trim().equals("")) { + showCompetitorFullNameColumn = false; + showCompetitorSailIdColumn = false; + } + } result = new LeaderboardSettings(maneuverDetails, legDetails, raceDetails, overallDetails, /* namesOfRaceColumnsToShow */ null, namesOfRacesToShow, numberOfLastRacesToShow, autoExpandPreSelectedRace, refreshIntervalMillis, /* sort by column */ (namesOfRacesToShow != null && !namesOfRacesToShow.isEmpty()) ? namesOfRacesToShow.get(0) : null, /* ascending */ true, /* updateUponPlayStateChange */ raceDetails.isEmpty() && legDetails.isEmpty(), - raceColumnSelectionStrategy, showAddedScores, showOverallColumnWithNumberOfRacesSailedPerCompetitor); + raceColumnSelectionStrategy, showAddedScores, showOverallColumnWithNumberOfRacesSailedPerCompetitor, + showCompetitorSailIdColumn, showCompetitorFullNameColumn); } else { final List overallDetails = Collections.singletonList(DetailType.REGATTA_RANK); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingToRaceLogDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingToRaceLogDialog.java index 2d14bf984cd..74aa4170a45 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingToRaceLogDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingToRaceLogDialog.java @@ -18,11 +18,11 @@ import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.domain.common.dto.CompetitorDTO; -import com.sap.sailing.domain.common.racelog.RaceLogServletConstants; import com.sap.sailing.domain.common.racelog.tracking.DeviceMappingConstants; import com.sap.sailing.domain.common.racelog.tracking.MappableToDevice; -import com.sap.sailing.gwt.ui.adminconsole.DeviceMappingQRCodeWidget.QRCodeURLCreationException; +import com.sap.sailing.domain.common.racelog.tracking.QRCodeURLCreationException; import com.sap.sailing.gwt.ui.adminconsole.ItemToMapToDeviceSelectionPanel.SelectionChangedHandler; +import com.sap.sailing.gwt.ui.client.GwtUrlHelper; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.BetterDateTimeBox; @@ -103,7 +103,7 @@ public class AddDeviceMappingToRaceLogDialog extends DataEntryDialog toMillis) { - throw new QRCodeURLCreationException("from cannt lie after to"); + throw new QRCodeURLCreationException("from can't lie after to"); } - return baseUrlWithoutTrailingSlash + DeviceMappingConstants.APK_PATH + "?" - + RaceLogServletConstants.PARAMS_LEADERBOARD_NAME + "=" + DeviceMappingQRCodeWidget.encode(leaderboardName) + "&" - + RaceLogServletConstants.PARAMS_RACE_COLUMN_NAME + "=" + DeviceMappingQRCodeWidget.encode(raceColumnName) - + "&" + RaceLogServletConstants.PARAMS_RACE_FLEET_NAME + "=" + DeviceMappingQRCodeWidget.encode(fleetName) - + "&" + mappedItemQueryParam + "&" + DeviceMappingConstants.URL_FROM_MILLIS + "=" + fromMillis - + "&" + DeviceMappingConstants.URL_TO_MILLIS + "=" + toMillis; + return DeviceMappingConstants.getDeviceMappingForRaceLogUrl(baseUrlWithoutTrailingSlash, + leaderboardName, raceColumnName, fleetName, mappedItemType, mappedItemId, fromMillis, toMillis, + GwtUrlHelper.INSTANCE); } }); qrWidget.generateQRCode(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingToRegattaLogDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingToRegattaLogDialog.java index 44fcd58eaf1..a23c10ea230 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingToRegattaLogDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingToRegattaLogDialog.java @@ -13,15 +13,16 @@ import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.VerticalPanel; import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.domain.common.racelog.tracking.DeviceMappingConstants; -import com.sap.sailing.gwt.ui.adminconsole.DeviceMappingQRCodeWidget.QRCodeURLCreationException; +import com.sap.sailing.domain.common.racelog.tracking.QRCodeURLCreationException; import com.sap.sailing.gwt.ui.adminconsole.ItemToMapToDeviceSelectionPanel.SelectionChangedHandler; +import com.sap.sailing.gwt.ui.client.GwtUrlHelper; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; -import com.sap.sailing.gwt.ui.common.client.GenericListBox; -import com.sap.sailing.gwt.ui.common.client.GenericListBox.ValueBuilder; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.MarkDTO; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.controls.GenericListBox; +import com.sap.sse.gwt.client.controls.GenericListBox.ValueBuilder; public class AddDeviceMappingToRegattaLogDialog extends AbstractCancelableDialog { private DeviceMappingQRCodeWidget qrWidget; @@ -35,11 +36,11 @@ public class AddDeviceMappingToRegattaLogDialog extends AbstractCancelableDialog setupUi(); center(); } - + @Override protected void addMainContent(Panel mainPanel) { super.addMainContent(mainPanel); - + CaptionPanel inputPanel = new CaptionPanel(); Grid inputGrid = new Grid(1, 2); inputPanel.add(inputGrid); @@ -62,13 +63,13 @@ public class AddDeviceMappingToRegattaLogDialog extends AbstractCancelableDialog public void onSuccess(Collection result) { events.addItems(result); } - + @Override public void onFailure(Throwable caught) { errorReporter.reportError("Could not load events: " + caught.getMessage()); } }); - + itemSelectionPanel = new ItemToMapToDeviceSelectionPanel(sailingService, stringMessages, errorReporter, new SelectionChangedHandler() { @Override @@ -85,33 +86,31 @@ public class AddDeviceMappingToRegattaLogDialog extends AbstractCancelableDialog } }, null); - //load table content + // load table content sailingService.getCompetitorRegistrations(leaderboardName, itemSelectionPanel.getSetCompetitorsCallback()); - //TODO marks from RegattaLog also? + // TODO marks from RegattaLog also? qrWidget = new DeviceMappingQRCodeWidget(stringMessages, new DeviceMappingQRCodeWidget.URLFactory() { @Override - public String createURL(String baseUrlWithoutTrailingSlash, String mappedItemQueryParam) + public String createURL(String baseUrlWithoutTrailingSlash, String mappedItemType, String mappedItemId) throws QRCodeURLCreationException { if (events.getValue() == null) { - throw new QRCodeURLCreationException("no event selected"); + throw new QRCodeURLCreationException(stringMessages.noEventSelected()); } String eventIdAsString = events.getValue().id.toString(); - return baseUrlWithoutTrailingSlash + DeviceMappingConstants.URL_BASE - + "?" + DeviceMappingConstants.URL_EVENT_ID + "=" + DeviceMappingQRCodeWidget.encode(eventIdAsString) - + "&" + DeviceMappingConstants.URL_LEADERBOARD_NAME + "=" + DeviceMappingQRCodeWidget.encode(leaderboardName) - + "&" + mappedItemQueryParam; + return DeviceMappingConstants.getDeviceMappingForRegattaLogUrl(baseUrlWithoutTrailingSlash, eventIdAsString, + leaderboardName, mappedItemType, mappedItemId, GwtUrlHelper.INSTANCE); } }); qrWidget.generateQRCode(); - + VerticalPanel leftSidePanel = new VerticalPanel(); leftSidePanel.add(inputPanel); CaptionPanel qrContentPanel = new CaptionPanel(); leftSidePanel.add(qrContentPanel); qrContentPanel.add(qrWidget); - + HorizontalPanel panel = new HorizontalPanel(); mainPanel.add(panel); panel.add(leftSidePanel); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java index 6c6261fa5e5..fbc4d4f6105 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java @@ -9,6 +9,8 @@ import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.ServiceDefTarget; import com.google.gwt.user.client.ui.RootLayoutPanel; import com.google.gwt.user.client.ui.TabLayoutPanel; +import com.sap.sailing.domain.common.security.Permission; +import com.sap.sailing.domain.common.security.SailingPermissionsForRoleProvider; import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint; import com.sap.sailing.gwt.ui.client.LeaderboardGroupsDisplayer; import com.sap.sailing.gwt.ui.client.LeaderboardGroupsRefresher; @@ -49,8 +51,8 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements } private void createUI() { - AdminConsolePanel panel = new AdminConsolePanel(getUserService(), sailingService, - getStringMessages().releaseNotes(), "/release_notes_admin.html", /* error reporter */ this, SecurityStylesheetResources.INSTANCE.css()); + AdminConsolePanel panel = new AdminConsolePanel(getUserService(), SailingPermissionsForRoleProvider.INSTANCE, + sailingService, getStringMessages().releaseNotes(), "/release_notes_admin.html", /* error reporter */ this, SecurityStylesheetResources.INSTANCE.css()); BetterDateTimeBox.initialize(); regattasDisplayers = new HashSet<>(); leaderboardsDisplayers = new HashSet<>(); @@ -63,7 +65,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements getWidget().fillEvents(); fillLeaderboardGroups(); } - }, getStringMessages().events(), SailingAdminConsoleFeatures.MANAGE_EVENTS); + }, getStringMessages().events(), Permission.MANAGE_EVENTS); leaderboardGroupsDisplayers.add(eventManagementPanel); RegattaManagementPanel regattaManagementPanel = new RegattaManagementPanel( @@ -74,13 +76,12 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements public void refreshAfterBecomingVisible() { fillRegattas(); } - }, getStringMessages().regattas(), SailingAdminConsoleFeatures.MANAGE_REGATTAS); + }, getStringMessages().regattas(), Permission.MANAGE_REGATTAS); regattasDisplayers.add(regattaManagementPanel); /* LEADERBOARDS */ - final TabLayoutPanel leaderboardTabPanel = panel.addVerticalTab(getStringMessages().leaderboards(), - "LeaderboardPanel", SailingAdminConsoleFeatures.MANAGE_LEADERBOARDS); + final TabLayoutPanel leaderboardTabPanel = panel.addVerticalTab(getStringMessages().leaderboards(), "LeaderboardPanel"); final LeaderboardConfigPanel leaderboardConfigPanel = new LeaderboardConfigPanel(sailingService, this, this, getStringMessages(), /* showRaceDetails */true, this); leaderboardConfigPanel.ensureDebugId("LeaderboardConfiguration"); @@ -89,7 +90,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements public void refreshAfterBecomingVisible() { fillLeaderboards(); } - }, getStringMessages().leaderboardConfiguration(), SailingAdminConsoleFeatures.MANAGE_LEADERBOARDS); + }, getStringMessages().leaderboardConfiguration(), Permission.MANAGE_LEADERBOARDS); regattasDisplayers.add(leaderboardConfigPanel); leaderboardsDisplayers.add(leaderboardConfigPanel); @@ -102,20 +103,19 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements fillLeaderboards(); fillLeaderboardGroups(); } - }, getStringMessages().leaderboardGroups(), SailingAdminConsoleFeatures.MANAGE_LEADERBOARD_GROUPS); + }, getStringMessages().leaderboardGroups(), Permission.MANAGE_LEADERBOARD_GROUPS); regattasDisplayers.add(leaderboardGroupConfigPanel); leaderboardGroupsDisplayers.add(leaderboardGroupConfigPanel); leaderboardsDisplayers.add(leaderboardGroupConfigPanel); /* RACES */ - final TabLayoutPanel racesTabPanel = panel.addVerticalTab(getStringMessages().races(), - "RacesPanel", SailingAdminConsoleFeatures.MANAGE_TRACKED_RACES); + final TabLayoutPanel racesTabPanel = panel.addVerticalTab(getStringMessages().races(), "RacesPanel"); TrackedRacesManagementPanel trackedRacesManagementPanel = new TrackedRacesManagementPanel(sailingService, this, this, getStringMessages()); trackedRacesManagementPanel.ensureDebugId("TrackedRacesManagement"); panel.addToTabPanel(racesTabPanel, new DefaultRefreshableAdminConsolePanel(trackedRacesManagementPanel), - getStringMessages().trackedRaces(), SailingAdminConsoleFeatures.SHOW_TRACKED_RACES); + getStringMessages().trackedRaces(), Permission.SHOW_TRACKED_RACES); regattasDisplayers.add(trackedRacesManagementPanel); final CompetitorPanel competitorPanel = new CompetitorPanel(sailingService, getStringMessages(), this); @@ -124,17 +124,17 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements public void refreshAfterBecomingVisible() { getWidget().refreshCompetitorList(); } - }, getStringMessages().competitors(), SailingAdminConsoleFeatures.MANAGE_ALL_COMPETITORS); + }, getStringMessages().competitors(), Permission.MANAGE_ALL_COMPETITORS); RaceCourseManagementPanel raceCourseManagementPanel = new RaceCourseManagementPanel(sailingService, this, this, getStringMessages()); - panel.addToTabPanel(racesTabPanel, new DefaultRefreshableAdminConsolePanel(raceCourseManagementPanel), getStringMessages().courseLayout(), SailingAdminConsoleFeatures.MANAGE_COURSE_LAYOUT); + panel.addToTabPanel(racesTabPanel, new DefaultRefreshableAdminConsolePanel(raceCourseManagementPanel), getStringMessages().courseLayout(), Permission.MANAGE_COURSE_LAYOUT); regattasDisplayers.add(raceCourseManagementPanel); final AsyncActionsExecutor asyncActionsExecutor = new AsyncActionsExecutor(); WindPanel windPanel = new WindPanel(sailingService, asyncActionsExecutor, this, this, getStringMessages()); panel.addToTabPanel(racesTabPanel, new DefaultRefreshableAdminConsolePanel(windPanel), getStringMessages().wind(), - SailingAdminConsoleFeatures.MANAGE_WIND); + Permission.MANAGE_WIND); regattasDisplayers.add(windPanel); final MediaPanel mediaPanel = new MediaPanel(regattasDisplayers, sailingService, this, mediaService, this, getStringMessages()); @@ -143,29 +143,28 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements public void refreshAfterBecomingVisible() { getWidget().onShow(); } - }, getStringMessages().mediaPanel(), SailingAdminConsoleFeatures.MANAGE_MEDIA); + }, getStringMessages().mediaPanel(), Permission.MANAGE_MEDIA); /* CONNECTORS */ - final TabLayoutPanel connectorsTabPanel = panel.addVerticalTab(getStringMessages().connectors(), - "TrackingProviderPanel", SailingAdminConsoleFeatures.MANAGE_TRACKED_RACES); + final TabLayoutPanel connectorsTabPanel = panel.addVerticalTab(getStringMessages().connectors(), "TrackingProviderPanel"); TracTracEventManagementPanel tractracEventManagementPanel = new TracTracEventManagementPanel(sailingService, this, this, getStringMessages()); tractracEventManagementPanel.ensureDebugId("TracTracEventManagement"); panel.addToTabPanel(connectorsTabPanel, new DefaultRefreshableAdminConsolePanel(tractracEventManagementPanel), - getStringMessages().tracTracEvents(), SailingAdminConsoleFeatures.MANAGE_TRACKED_RACES); + getStringMessages().tracTracEvents(), Permission.MANAGE_TRACKED_RACES); regattasDisplayers.add(tractracEventManagementPanel); SwissTimingReplayConnectorPanel swissTimingReplayConnectorPanel = new SwissTimingReplayConnectorPanel( sailingService, this, this, getStringMessages()); panel.addToTabPanel(connectorsTabPanel, new DefaultRefreshableAdminConsolePanel(swissTimingReplayConnectorPanel), - getStringMessages().swissTimingArchiveConnector(), SailingAdminConsoleFeatures.MANAGE_TRACKED_RACES); + getStringMessages().swissTimingArchiveConnector(), Permission.MANAGE_TRACKED_RACES); regattasDisplayers.add(swissTimingReplayConnectorPanel); SwissTimingEventManagementPanel swisstimingEventManagementPanel = new SwissTimingEventManagementPanel( sailingService, this, this, getStringMessages()); panel.addToTabPanel(connectorsTabPanel, new DefaultRefreshableAdminConsolePanel(swisstimingEventManagementPanel), - getStringMessages().swissTimingEvents(), SailingAdminConsoleFeatures.MANAGE_TRACKED_RACES); + getStringMessages().swissTimingEvents(), Permission.MANAGE_TRACKED_RACES); regattasDisplayers.add(swisstimingEventManagementPanel); final RaceLogTrackingEventManagementPanel raceLogTrackingEventManagementPanel = new RaceLogTrackingEventManagementPanel( @@ -175,56 +174,56 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements public void refreshAfterBecomingVisible() { fillLeaderboards(); } - }, getStringMessages().raceLogTracking(), SailingAdminConsoleFeatures.MANAGE_TRACKED_RACES); + }, getStringMessages().raceLogTracking(), Permission.MANAGE_TRACKED_RACES); regattasDisplayers.add(raceLogTrackingEventManagementPanel); leaderboardsDisplayers.add(raceLogTrackingEventManagementPanel); IgtimiAccountsPanel igtimiAccountsPanel = new IgtimiAccountsPanel(sailingService, this, getStringMessages()); igtimiAccountsPanel.ensureDebugId("IgtimiAccounts"); panel.addToTabPanel(connectorsTabPanel, new DefaultRefreshableAdminConsolePanel(igtimiAccountsPanel), - getStringMessages().igtimiAccounts(), SailingAdminConsoleFeatures.MANAGE_IGTIMI_ACCOUNTS); + getStringMessages().igtimiAccounts(), Permission.MANAGE_IGTIMI_ACCOUNTS); ResultImportUrlsManagementPanel resultImportUrlsManagementPanel = new ResultImportUrlsManagementPanel(sailingService, this, getStringMessages()); panel.addToTabPanel(connectorsTabPanel, new DefaultRefreshableAdminConsolePanel(resultImportUrlsManagementPanel), - getStringMessages().resultImportUrls(), SailingAdminConsoleFeatures.MANAGE_RESULT_IMPORT_URLS); + getStringMessages().resultImportUrls(), Permission.MANAGE_RESULT_IMPORT_URLS); StructureImportManagementPanel structureImportUrlsManagementPanel = new StructureImportManagementPanel(sailingService, this, getStringMessages(), this, eventManagementPanel); panel.addToTabPanel(connectorsTabPanel, new DefaultRefreshableAdminConsolePanel(structureImportUrlsManagementPanel), - getStringMessages().structureImportUrls(), SailingAdminConsoleFeatures.MANAGE_STRUCTURE_IMPORT_URLS); + getStringMessages().structureImportUrls(), Permission.MANAGE_STRUCTURE_IMPORT_URLS); /* ADVANCED */ - final TabLayoutPanel advancedTabPanel = panel.addVerticalTab(getStringMessages().advanced(), - "AdvancedPanel", SailingAdminConsoleFeatures.MANAGE_REPLICATION); + final TabLayoutPanel advancedTabPanel = panel.addVerticalTab(getStringMessages().advanced(), "AdvancedPanel"); final ReplicationPanel replicationPanel = new ReplicationPanel(sailingService, this, getStringMessages()); panel.addToTabPanel(advancedTabPanel, new DefaultRefreshableAdminConsolePanel(replicationPanel) { @Override public void refreshAfterBecomingVisible() { replicationPanel.updateReplicaList(); } - }, getStringMessages().replication(), SailingAdminConsoleFeatures.MANAGE_REPLICATION); + }, getStringMessages().replication(), Permission.MANAGE_REPLICATION); final MasterDataImportPanel masterDataImportPanel = new MasterDataImportPanel(getStringMessages(), sailingService, this, eventManagementPanel, this, this); panel.addToTabPanel(advancedTabPanel, new DefaultRefreshableAdminConsolePanel(masterDataImportPanel), - getStringMessages().masterDataImportPanel(), SailingAdminConsoleFeatures.MANAGE_MASTERDATA_IMPORT); + getStringMessages().masterDataImportPanel(), Permission.MANAGE_MASTERDATA_IMPORT); RemoteSailingServerInstancesManagementPanel sailingServerInstancesManagementPanel = new RemoteSailingServerInstancesManagementPanel(sailingService, this, getStringMessages()); panel.addToTabPanel(advancedTabPanel, new DefaultRefreshableAdminConsolePanel(sailingServerInstancesManagementPanel), - getStringMessages().sailingServers(), SailingAdminConsoleFeatures.MANAGE_SAILING_SERVER_INSTANCES); + getStringMessages().sailingServers(), Permission.MANAGE_SAILING_SERVER_INSTANCES); final DeviceConfigurationUserPanel deviceConfigurationUserPanel = new DeviceConfigurationUserPanel(sailingService, getStringMessages(), this); panel.addToTabPanel(advancedTabPanel, new DefaultRefreshableAdminConsolePanel(deviceConfigurationUserPanel), - getStringMessages().deviceConfiguration(), SailingAdminConsoleFeatures.MANAGE_DEVICE_CONFIGURATION); + getStringMessages().deviceConfiguration(), Permission.MANAGE_DEVICE_CONFIGURATION); - final UserManagementPanel userManagementPanel = new UserManagementPanel(getUserService(), StringMessages.INSTANCE); + final UserManagementPanel userManagementPanel = new UserManagementPanel(getUserService(), StringMessages.INSTANCE, + SailingPermissionsForRoleProvider.INSTANCE); panel.addToTabPanel(advancedTabPanel, new DefaultRefreshableAdminConsolePanel(userManagementPanel), - getStringMessages().userManagement(), SailingAdminConsoleFeatures.MANAGE_USERS); + getStringMessages().userManagement(), Permission.MANAGE_USERS); final FileStoragePanel fileStoragePanel = new FileStoragePanel(sailingService, this); panel.addToTabPanel(advancedTabPanel, new DefaultRefreshableAdminConsolePanel(fileStoragePanel), - getStringMessages().fileStorage(), SailingAdminConsoleFeatures.MANAGE_FILE_STORAGE); + getStringMessages().fileStorage(), Permission.MANAGE_FILE_STORAGE); panel.initUI(); fillRegattas(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleResources.java index 8d9424d2202..7dc5b7fbbc7 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleResources.java @@ -11,9 +11,6 @@ interface AdminConsoleResources extends ClientBundle { @Source("com/sap/sailing/gwt/ui/client/images/settingsActionIcon.png") ImageResource settingsActionIcon(); - @Source("com/sap/sse/gwt/client/images/remove.png") - ImageResource removeIcon(); - @Source("com/sap/sailing/gwt/ui/client/images/edit.png") ImageResource editIcon(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/BaseQRIdentifierWidget.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/BaseQRIdentifierWidget.java index 143472a1efe..8f73b604e63 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/BaseQRIdentifierWidget.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/BaseQRIdentifierWidget.java @@ -4,7 +4,6 @@ import com.google.gwt.event.dom.client.KeyUpEvent; import com.google.gwt.event.dom.client.KeyUpHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; -import com.google.gwt.http.client.URL; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Grid; import com.google.gwt.user.client.ui.HasHorizontalAlignment; @@ -13,7 +12,7 @@ import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.gwt.ui.adminconsole.DeviceMappingQRCodeWidget.QRCodeURLCreationException; +import com.sap.sailing.domain.common.racelog.tracking.QRCodeURLCreationException; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.QRCodeComposite; @@ -91,9 +90,4 @@ public abstract class BaseQRIdentifierWidget implements IsWidget { protected void setError(String text) { error.setText(text); } - - public static String encode(String value) { - //pathSegment instead of queryString, so that ' ' is encoded as '%20' - return URL.encodePathSegment(value); - } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java index f6742beb834..75d41369a88 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java @@ -20,6 +20,7 @@ import com.sap.sse.common.CountryCode; import com.sap.sse.common.CountryCodeFactory; import com.sap.sse.common.Util; import com.sap.sse.common.impl.RGBColor; +import com.sap.sse.gwt.adminconsole.URLFieldWithFileUpload; import com.sap.sse.gwt.client.dialog.DataEntryDialog; /** @@ -38,11 +39,13 @@ public class CompetitorEditDialog extends DataEntryDialog { private final TextBox displayColorTextBox; private final ListBox threeLetterIocCountryCode; private final TextBox sailId; + private final TextBox email; private final StringMessages stringMessages; + private final URLFieldWithFileUpload imageUrlAndUploadComposite; public CompetitorEditDialog(final StringMessages stringMessages, CompetitorDTO competitorToEdit, DialogCallback callback) { - super(stringMessages.editCompetitor(), stringMessages.editCompetitor(), stringMessages.ok(), stringMessages + super(stringMessages.editCompetitor(), null, stringMessages.ok(), stringMessages .cancel(), new Validator() { @Override public String getErrorMessage(CompetitorDTO valueToValidate) { @@ -89,6 +92,7 @@ public class CompetitorEditDialog extends DataEntryDialog { } this.name = createTextBox(competitorToEdit.getName()); + this.email = createTextBox(competitorToEdit.getEmail()); this.displayColorTextBox = createTextBox(competitorToEdit.getColor() == null ? "" : competitorToEdit.getColor().getAsHtml()); this.threeLetterIocCountryCode = createListBox(/* isMultipleSelect */ false); CountryCodeFactory ccf = CountryCodeFactory.INSTANCE; @@ -118,6 +122,8 @@ public class CompetitorEditDialog extends DataEntryDialog { } } this.sailId = createTextBox(competitorToEdit.getSailID()); + imageUrlAndUploadComposite = new URLFieldWithFileUpload(stringMessages); + imageUrlAndUploadComposite.setURL(competitorToEdit.getImageURL()); } @@ -170,17 +176,17 @@ public class CompetitorEditDialog extends DataEntryDialog { } } BoatClassDTO boatClass = new BoatClassDTO(boatClassName.getValue(boatClassName.getSelectedIndex()), 0); - CompetitorDTO result = new CompetitorDTOImpl(name.getText(), color, + CompetitorDTO result = new CompetitorDTOImpl(name.getText(), color, email.getText(), /* twoLetterIsoCountryCode */ null, threeLetterIocCountryCode.getValue(threeLetterIocCountryCode.getSelectedIndex()), /* countryName */ null, sailId.getText(), competitorToEdit.getIdAsString(), - boatClass); + imageUrlAndUploadComposite.getURL(), boatClass); return result; } @Override protected Widget getAdditionalWidget() { - Grid result = new Grid(5, 2); + Grid result = new Grid(7, 2); result.setWidget(0, 0, new Label(stringMessages.name())); result.setWidget(0, 1, name); result.setWidget(1, 0, new Label(stringMessages.sailNumber())); @@ -189,8 +195,12 @@ public class CompetitorEditDialog extends DataEntryDialog { result.setWidget(2, 1, threeLetterIocCountryCode); result.setWidget(3, 0, new Label(stringMessages.color())); result.setWidget(3, 1, displayColorTextBox); - result.setWidget(4, 0, new Label(stringMessages.boatClass())); - result.setWidget(4, 1, boatClassName); + result.setWidget(4, 0, new Label(stringMessages.email())); + result.setWidget(4, 1, email); + result.setWidget(5, 0, new Label(stringMessages.boatClass())); + result.setWidget(5, 1, boatClassName); + result.setWidget(6, 0, new Label(stringMessages.imageURL())); + result.setWidget(6, 1, imageUrlAndUploadComposite); return result; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java index 4b0dc656446..51ad785a15d 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java @@ -1,18 +1,24 @@ package com.sap.sailing.gwt.ui.adminconsole; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Set; import com.google.gwt.cell.client.FieldUpdater; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.i18n.client.LocaleInfo; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.Grid; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; +import com.google.gwt.user.client.ui.Widget; import com.google.gwt.view.client.MultiSelectionModel; import com.google.gwt.view.client.SelectionChangeEvent; import com.google.gwt.view.client.SelectionChangeEvent.Handler; @@ -20,8 +26,13 @@ import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.domain.common.dto.CompetitorDTOImpl; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sse.common.Util; +import com.sap.sse.common.Util.Pair; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.controls.GenericListBox; +import com.sap.sse.gwt.client.controls.GenericListBox.ValueBuilder; +import com.sap.sse.gwt.client.dialog.DataEntryDialog; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; /** @@ -43,15 +54,17 @@ public class CompetitorPanel extends SimplePanel { this(sailingService, null, stringMessages, errorReporter); } - public CompetitorPanel(final SailingServiceAsync sailingService, String leaderboardName, final StringMessages stringMessages, - final ErrorReporter errorReporter) { + public CompetitorPanel(final SailingServiceAsync sailingService, final String leaderboardName, + final StringMessages stringMessages, final ErrorReporter errorReporter) { super(); this.sailingService = sailingService; this.stringMessages = stringMessages; this.errorReporter = errorReporter; this.leaderboardName = leaderboardName; - this.competitorTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, - new MultiSelectionModel(), false); + final Class selectionModelType = MultiSelectionModel.class; + @SuppressWarnings("unchecked") + final Class> multiSelectionType = (Class>) selectionModelType; + this.competitorTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, multiSelectionType, false); this.competitorSelectionModel = competitorTable.getSelectionModel(); VerticalPanel mainPanel = new VerticalPanel(); this.setWidget(mainPanel); @@ -79,16 +92,56 @@ public class CompetitorPanel extends SimplePanel { buttonPanel.add(allowReloadButton); Button addCompetitorButton = new Button(stringMessages.add()); addCompetitorButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - openAddCompetitorDialog(); - } - }); + @Override + public void onClick(ClickEvent event) { + openAddCompetitorDialog(); + } + }); buttonPanel.add(addCompetitorButton); + + Button selectAllButton = new Button(stringMessages.selectAll()); + selectAllButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + for (CompetitorDTO c : competitorTable.getDataProvider().getList()) { + competitorSelectionModel.setSelected(c, true); + } + } + }); + buttonPanel.add(selectAllButton); + + //only if this competitor panel is connected to a leaderboard, we want to enable invitations + if (leaderboardName != null) { + final Button inviteCompetitorsButton = new Button(stringMessages.inviteCompetitors()); + inviteCompetitorsButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + Set competitors = competitorSelectionModel.getSelectedSet(); + boolean emailProvidedForAll = isEmailProvidedForAll(competitors); + + if (emailProvidedForAll) { + openChooseEventDialogAndSendMails(competitors); + } else { + Window.alert(stringMessages.notAllCompetitorsProvideEmail()); + } + } + + private boolean isEmailProvidedForAll(Iterable allCompetitors) { + for (CompetitorDTO competitor : allCompetitors) { + if (!competitor.hasEmail()) { + return false; + } + } + + return true; + } + }); + buttonPanel.add(inviteCompetitorsButton); + } + competitorsPanel.add(buttonPanel); - - //competitor table + // competitor table ImagesBarColumn competitorActionColumn = new ImagesBarColumn( new CompetitorConfigImagesBarCell(stringMessages)); competitorActionColumn.setFieldUpdater(new FieldUpdater() { @@ -112,21 +165,24 @@ public class CompetitorPanel extends SimplePanel { } }); allowReloadButton.setEnabled(!competitorSelectionModel.getSelectedSet().isEmpty()); - - if(leaderboardName != null) { + + if (leaderboardName != null) { refreshCompetitorList(); } } - + private void allowUpdate(final Iterable competitors) { List serializableSingletonList = new ArrayList(); Util.addAll(competitors, serializableSingletonList); sailingService.allowCompetitorResetToDefaults(serializableSingletonList, new AsyncCallback() { - @Override public void onFailure(Throwable caught) { - errorReporter.reportError("Error trying to allow resetting competitors "+competitors - +" to defaults: "+caught.getMessage()); + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Error trying to allow resetting competitors " + competitors + + " to defaults: " + caught.getMessage()); } - @Override public void onSuccess(Void result) { + + @Override + public void onSuccess(Void result) { Window.alert(stringMessages.successfullyAllowedCompetitorReset(competitors.toString())); } }); @@ -143,7 +199,7 @@ public class CompetitorPanel extends SimplePanel { sailingService.addOrUpdateCompetitor(competitor, new AsyncCallback() { @Override public void onFailure(Throwable caught) { - errorReporter.reportError("Error trying to update competitor: "+caught.getMessage()); + errorReporter.reportError("Error trying to update competitor: " + caught.getMessage()); } @Override @@ -159,6 +215,101 @@ public class CompetitorPanel extends SimplePanel { }).show(); } + private class SelectEventAndHostnameDialog extends DataEntryDialog> { + private GenericListBox events; + private TextBox serverUrl; + + public SelectEventAndHostnameDialog(DialogCallback> callback) { + super(stringMessages.selectEventForInvitation(), null, stringMessages.inviteCompetitors(), stringMessages.cancel(), + new Validator>() { + @Override + public String getErrorMessage(Pair valueToValidate) { + if (valueToValidate.getA() == null) { + return stringMessages.pleaseSelectAnEvent(); + } else if (valueToValidate.getB() == null || valueToValidate.getB().isEmpty()) { + return stringMessages.pleaseEnterA(stringMessages.hostname()); + } + return null; + } + }, false, callback); + } + + @Override + protected Pair getResult() { + EventDTO event = events.getValue(); + String serverUrlString = serverUrl.getValue(); + if (serverUrlString.endsWith("/")) { + serverUrlString = serverUrlString.substring(0, serverUrlString.length() - 1); + } + return new Pair<>(event, serverUrlString); + } + + @Override + protected Widget getAdditionalWidget() { + Grid grid = new Grid(2, 2); + grid.setWidget(0, 0, createLabel(stringMessages.hostname())); + serverUrl = createTextBox(Window.Location.getProtocol() + "//" + Window.Location.getHost()); + grid.setWidget(0, 1, serverUrl); + + grid.setWidget(1, 0, createLabel(stringMessages.event())); + events = createGenericListBox(new ValueBuilder() { + @Override + public String getValue(EventDTO item) { + if (item == null) { + return ""; + } + return item.getName(); + } + }, false); + grid.setWidget(1, 1, events); + + events.addItem((EventDTO) null); + sailingService.getEventsForLeaderboard(leaderboardName, new AsyncCallback>() { + @Override + public void onSuccess(Collection result) { + events.addItems(result); + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Could not load events: " + caught.getMessage()); + } + }); + return grid; + } + } + + private String getLocaleInfo() { + return LocaleInfo.getCurrentLocale().getLocaleName(); + } + + private void openChooseEventDialogAndSendMails(final Set competitors) { + new SelectEventAndHostnameDialog(new DialogCallback>() { + + @Override + public void ok(Pair result) { + sailingService.inviteCompetitorsForTrackingViaEmail(result.getB(), result.getA(), leaderboardName, + competitors, getLocaleInfo(), new AsyncCallback() { + + @Override + public void onFailure(Throwable caught) { + Window.alert(stringMessages.sendingMailsFailed() + caught.getMessage()); + } + + @Override + public void onSuccess(Void result) { + Window.alert(stringMessages.sendingMailsSuccessfull()); + } + }); + } + + @Override + public void cancel() { + + } + }).show(); + } + public void refreshCompetitorList() { competitorTable.refreshCompetitorList(leaderboardName); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java index 2f1acd06de2..3cf9d4423cb 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java @@ -6,6 +6,7 @@ import java.util.List; import com.google.gwt.cell.client.SafeHtmlCell; import com.google.gwt.core.client.Callback; +import com.google.gwt.core.client.GWT; import com.google.gwt.resources.client.ImageResource; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; @@ -15,23 +16,32 @@ import com.google.gwt.user.cellview.client.TextColumn; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.ImageResourceRenderer; import com.google.gwt.user.client.ui.Label; +import com.google.gwt.view.client.ListDataProvider; import com.google.gwt.view.client.SelectionModel; +import com.google.gwt.view.client.SingleSelectionModel; import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.domain.common.impl.NaturalComparator; +import com.sap.sailing.gwt.ui.adminconsole.AdminConsoleTableResources.AdminConsoleTableStyle; import com.sap.sailing.gwt.ui.adminconsole.ColorColumn.ColorRetriever; import com.sap.sailing.gwt.ui.client.FlagImageResolver; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn; import com.sap.sse.common.Color; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; public class CompetitorTableWrapper> extends TableWrapper { + private static final AdminConsoleTableResources tableResources = GWT.create(AdminConsoleTableResources.class); + private static final AdminConsoleTableStyle tableSTyle = tableResources.cellTableStyle(); + private final LabeledAbstractFilterablePanel filterField; + + private final S selectionModel; public CompetitorTableWrapper(SailingServiceAsync sailingService, StringMessages stringMessages,ErrorReporter errorReporter, - S selectionModel, boolean enablePager) { - super(sailingService, stringMessages, errorReporter, selectionModel, enablePager); + Class selectionModelType, boolean enablePager) { + super(sailingService, stringMessages, errorReporter, /* selectionModel will be set later below */ null, enablePager); ListHandler competitorColumnListHandler = new ListHandler(dataProvider.getList()); @@ -101,14 +111,32 @@ public class CompetitorTableWrapper> ext } }); + Column imageColumn = new Column(new SafeHtmlCell()) { + @Override + public SafeHtml getValue(CompetitorDTO competitor) { + SafeHtmlBuilder sb = new SafeHtmlBuilder(); + if (competitor.getImageURL() != null && !competitor.getImageURL().isEmpty()) { + sb.appendHtmlConstant(""); + } + return sb.toSafeHtml(); + } + }; + imageColumn.setSortable(true); + competitorColumnListHandler.setComparator(imageColumn, new Comparator() { + private final NaturalComparator comparator = new NaturalComparator(/* case sensitive */ false); + @Override + public int compare(CompetitorDTO o1, CompetitorDTO o2) { + return comparator.compare(o1.getImageURL(), o2.getImageURL()); + } + }); + TextColumn competitorIdColumn = new TextColumn() { @Override public String getValue(CompetitorDTO competitor) { return competitor.getIdAsString(); } - }; - competitorIdColumn.setSortable(true); competitorColumnListHandler.setComparator(competitorIdColumn, new Comparator() { @Override @@ -117,9 +145,22 @@ public class CompetitorTableWrapper> ext } }); + TextColumn competitorEMailColumn = new TextColumn() { + @Override + public String getValue(CompetitorDTO competitor) { + return competitor.getEmail(); + } + }; + competitorEMailColumn.setSortable(true); + competitorColumnListHandler.setComparator(competitorEMailColumn, new Comparator() { + @Override + public int compare(CompetitorDTO o1, CompetitorDTO o2) { + return new NaturalComparator(false).compare(o1.getEmail(), o2.getEmail()); + } + }); + filterField = new LabeledAbstractFilterablePanel(new Label(stringMessages.filterCompetitors()), new ArrayList(), table, dataProvider) { - @Override public Iterable getSearchableStrings(CompetitorDTO t) { List string = new ArrayList(); @@ -131,14 +172,41 @@ public class CompetitorTableWrapper> ext }; mainPanel.insert(filterField, 0); + if (selectionModelType.getName().contains("Multi")) { + // add a selection checkbox column + SelectionCheckboxColumn selectionCheckboxColumn = new SelectionCheckboxColumn( + tableSTyle.cellTableCheckboxSelected(), tableSTyle.cellTableCheckboxDeselected(), tableSTyle.cellTableCheckboxColumnCell()) { + @Override + protected ListDataProvider getListDataProvider() { + return dataProvider; + } + }; + @SuppressWarnings("unchecked") + S mySelectionModel = (S) selectionCheckboxColumn.getSelectionModel(); + selectionModel = mySelectionModel; + table.setSelectionModel(selectionModel, selectionCheckboxColumn.getSelectionManager()); + table.addColumn(selectionCheckboxColumn, selectionCheckboxColumn.getHeader()); + } else { + @SuppressWarnings("unchecked") + S mySelectionModel = (S) new SingleSelectionModel(); + selectionModel = mySelectionModel; + table.setSelectionModel(selectionModel); + } table.addColumnSortHandler(competitorColumnListHandler); table.addColumn(sailIdColumn, stringMessages.sailNumber()); table.addColumn(competitorNameColumn, stringMessages.name()); table.addColumn(boatClassColumn, stringMessages.boatClass()); table.addColumn(displayColorColumn, stringMessages.color()); + table.addColumn(imageColumn, stringMessages.imageURL()); + table.addColumn(competitorEMailColumn, stringMessages.email()); table.addColumn(competitorIdColumn, stringMessages.id()); } + @Override + public S getSelectionModel() { + return selectionModel; + } + public Iterable getAllCompetitors() { return filterField.getAll(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CourseManagementWidgetWaypointsImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CourseManagementWidgetWaypointsImagesBarCell.java index 6f7e9c87be9..97463fc4117 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CourseManagementWidgetWaypointsImagesBarCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CourseManagementWidgetWaypointsImagesBarCell.java @@ -2,13 +2,12 @@ package com.sap.sailing.gwt.ui.adminconsole; import java.util.ArrayList; -import com.google.gwt.core.client.GWT; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.ImagesBarCell; +import com.sap.sse.gwt.client.IconResources; public class CourseManagementWidgetWaypointsImagesBarCell extends ImagesBarCell { public static final String ACTION_DELETE = "ACTION_DELETE"; - private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); private final StringMessages stringMessages; public CourseManagementWidgetWaypointsImagesBarCell(StringMessages stringMessages) { @@ -19,7 +18,7 @@ public class CourseManagementWidgetWaypointsImagesBarCell extends ImagesBarCell @Override protected Iterable getImageSpecs() { ArrayList result = new ArrayList(); - result.add(new ImageSpec(ACTION_DELETE, stringMessages.remove(), makeImagePrototype(resources.removeIcon()))); + result.add(new ImageSpec(ACTION_DELETE, stringMessages.remove(), makeImagePrototype(IconResources.INSTANCE.removeIcon()))); return result; } } \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DefaultRegattaCreateDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DefaultRegattaCreateDialog.java index 0c061b9c8eb..0b42afc7fb7 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DefaultRegattaCreateDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DefaultRegattaCreateDialog.java @@ -2,7 +2,6 @@ package com.sap.sailing.gwt.ui.adminconsole; import java.util.List; -import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.ui.TabPanel; import com.google.gwt.user.client.ui.VerticalPanel; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; @@ -12,12 +11,11 @@ import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.SeriesDTO; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.IconResources; import com.sap.sse.gwt.client.controls.listedit.ListEditorComposite; public class DefaultRegattaCreateDialog extends AbstractRegattaWithSeriesAndFleetsDialog { - private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); - public DefaultRegattaCreateDialog(List existingEvents, RegattaDTO selectedRegatta, SailingServiceAsync sailingService, ErrorReporter errorReporter, StringMessages stringMessages, com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback callback) { @@ -34,7 +32,7 @@ public class DefaultRegattaCreateDialog extends AbstractRegattaWithSeriesAndFlee } protected ListEditorComposite createSeriesEditor(Iterable series) { - return new SeriesWithFleetsDefaultListEditor(series, stringMessages, resources.removeIcon(), /* enableFleetRemoval */ false); + return new SeriesWithFleetsDefaultListEditor(series, stringMessages, IconResources.INSTANCE.removeIcon(), /* enableFleetRemoval */ false); } protected void setupAdditionalWidgetsOnPanel(final VerticalPanel panel) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationCreateMatcherDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationCreateMatcherDialog.java index 5614548ea1c..3ba15679621 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationCreateMatcherDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationCreateMatcherDialog.java @@ -5,7 +5,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.event.dom.client.ClickEvent; @@ -20,6 +19,7 @@ import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.domain.common.configuration.DeviceConfigurationMatcherType; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.DeviceConfigurationMatcherDTO; +import com.sap.sse.gwt.client.IconResources; import com.sap.sse.gwt.client.dialog.DataEntryDialog; public class DeviceConfigurationCreateMatcherDialog extends DataEntryDialog { @@ -63,7 +63,6 @@ public class DeviceConfigurationCreateMatcherDialog extends DataEntryDialog devicesBoxes; @@ -164,7 +163,7 @@ public class DeviceConfigurationCreateMatcherDialog extends DataEntryDialog initialValues = originalConfiguration.allowedCourseAreaNames == null ? Collections . emptyList() : originalConfiguration.allowedCourseAreaNames; - allowedCourseAreasList = new StringListEditorComposite(initialValues, stringMessages, stringMessages.courseAreas(), resources.removeIcon(), + allowedCourseAreasList = new StringListEditorComposite(initialValues, stringMessages, stringMessages.courseAreas(), IconResources.INSTANCE.removeIcon(), SuggestedCourseAreaNames.suggestedCourseAreaNames, stringMessages.enterCourseAreaName()); allowedCourseAreasList.setWidth("80%"); allowedCourseAreasList.addValueChangeHandler(dirtyValueMarker); @@ -219,7 +220,7 @@ public class DeviceConfigurationDetailComposite extends Composite { List initialValues = originalConfiguration.byNameDesignerCourseNames == null ? Collections . emptyList() : originalConfiguration.byNameDesignerCourseNames; - courseNamesList = new StringListEditorComposite(initialValues, stringMessages, stringMessages.courseNames(), resources.removeIcon(), suggestedCourseNames, + courseNamesList = new StringListEditorComposite(initialValues, stringMessages, stringMessages.courseNames(), IconResources.INSTANCE.removeIcon(), suggestedCourseNames, stringMessages.enterCourseName()); courseNamesList.setWidth("80%"); courseNamesList.addValueChangeHandler(dirtyValueMarker); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceMappingQRCodeWidget.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceMappingQRCodeWidget.java index 4cdc985eac3..20815e78d99 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceMappingQRCodeWidget.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceMappingQRCodeWidget.java @@ -2,6 +2,7 @@ package com.sap.sailing.gwt.ui.adminconsole; import com.google.gwt.http.client.URL; import com.sap.sailing.domain.common.racelog.tracking.DeviceMappingConstants; +import com.sap.sailing.domain.common.racelog.tracking.QRCodeURLCreationException; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.QRCodeComposite; @@ -13,15 +14,8 @@ public class DeviceMappingQRCodeWidget extends BaseQRIdentifierWidget { private String mappedItemId; private String mappedItemType; - static class QRCodeURLCreationException extends Exception { - private static final long serialVersionUID = -8243995470804772458L; - public QRCodeURLCreationException(String message) { - super(message); - } - } - static interface URLFactory { - String createURL(String baseUrlWithoutTrailingSlash, String mappedItemQueryParam) + String createURL(String baseUrlWithoutTrailingSlash, String mappedItemType, String mappedItemId) throws QRCodeURLCreationException; } @@ -52,6 +46,6 @@ public class DeviceMappingQRCodeWidget extends BaseQRIdentifierWidget { throw new QRCodeURLCreationException("No item selected for mapping"); } - return urlFactory.createURL(serverUrl, mappedItemType + "=" + encode(mappedItemId)); + return urlFactory.createURL(serverUrl, mappedItemType, mappedItemId); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventConfigImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventConfigImagesBarCell.java index 3c6325ab3b8..d1dd0711f96 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventConfigImagesBarCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventConfigImagesBarCell.java @@ -6,6 +6,7 @@ import com.google.gwt.core.client.GWT; import com.google.gwt.text.shared.SafeHtmlRenderer; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.ImagesBarCell; +import com.sap.sse.gwt.client.IconResources; public class EventConfigImagesBarCell extends ImagesBarCell { static final String ACTION_REMOVE = "ACTION_REMOVE"; @@ -27,7 +28,7 @@ public class EventConfigImagesBarCell extends ImagesBarCell { protected Iterable getImageSpecs() { return Arrays.asList( new ImageSpec(ACTION_EDIT, stringMessages.actionEdit(), makeImagePrototype(resources.editIcon())), - new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(resources.removeIcon())) + new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(IconResources.INSTANCE.removeIcon())) ); } } \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDialog.java index cb573c4c8c6..74d0956b7bc 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDialog.java @@ -8,7 +8,6 @@ import java.util.Date; import java.util.List; import java.util.UUID; -import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; @@ -28,11 +27,11 @@ import com.sap.sailing.gwt.ui.shared.CourseAreaDTO; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.VenueDTO; +import com.sap.sse.gwt.client.IconResources; import com.sap.sse.gwt.client.controls.listedit.StringConstantsListEditorComposite; import com.sap.sse.gwt.client.controls.listedit.StringListInlineEditorComposite; public abstract class EventDialog extends DataEntryDialogWithBootstrap { - private final AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); protected StringMessages stringMessages; protected TextBox nameEntryField; protected TextArea descriptionEntryField; @@ -131,22 +130,22 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap }; courseAreaNameList = new StringListInlineEditorComposite(Collections. emptyList(), - new StringListInlineEditorComposite.ExpandedUi(stringMessages, resources.removeIcon(), /* suggestValues */ + new StringListInlineEditorComposite.ExpandedUi(stringMessages, IconResources.INSTANCE.removeIcon(), /* suggestValues */ SuggestedCourseAreaNames.suggestedCourseAreaNames, stringMessages.enterCourseAreaName(), 50)); courseAreaNameList.addValueChangeHandler(valueChangeHandler); final List imageSuggestionURLs = Arrays.asList(new String[] { "http://", "https://", "http://www.", "https://www" }); imageURLList = new StringListInlineEditorComposite(Collections. emptyList(), - new StringListInlineEditorComposite.ExpandedUi(stringMessages, resources.removeIcon(), + new StringListInlineEditorComposite.ExpandedUi(stringMessages, IconResources.INSTANCE.removeIcon(), /* suggestValues */ imageSuggestionURLs, stringMessages.enterImageURL(), 80)); imageURLList.addValueChangeHandler(valueChangeHandler); List videoURLSuggestions = new ArrayList<>(imageSuggestionURLs); videoURLSuggestions.add("http://www.youtube.com/watch?v="); videoURLList = new StringListInlineEditorComposite(Collections. emptyList(), - new StringListInlineEditorComposite.ExpandedUi(stringMessages, resources.removeIcon(), + new StringListInlineEditorComposite.ExpandedUi(stringMessages, IconResources.INSTANCE.removeIcon(), /* suggestValues */ videoURLSuggestions, stringMessages.enterVideoURL(), 80)); videoURLList.addValueChangeHandler(valueChangeHandler); sponsorImageURLList = new StringListInlineEditorComposite(Collections. emptyList(), - new StringListInlineEditorComposite.ExpandedUi(stringMessages, resources.removeIcon(), + new StringListInlineEditorComposite.ExpandedUi(stringMessages, IconResources.INSTANCE.removeIcon(), /* suggestValues */ imageSuggestionURLs, stringMessages.enterSponsorImageURL(), 80)); sponsorImageURLList.addValueChangeHandler(valueChangeHandler); List leaderboardGroupNames = new ArrayList<>(); @@ -154,7 +153,7 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap leaderboardGroupNames.add(leaderboardGroupDTO.getName()); } leaderboardGroupList = new StringConstantsListEditorComposite(Collections. emptyList(), - new StringConstantsListEditorComposite.ExpandedUi(stringMessages, resources.removeIcon(), + new StringConstantsListEditorComposite.ExpandedUi(stringMessages, IconResources.INSTANCE.removeIcon(), leaderboardGroupNames, "Select a leaderboard group...")); leaderboardGroupList.addValueChangeHandler(valueChangeHandler); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/IgtimiAccountsPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/IgtimiAccountsPanel.java index 375a7612bd4..424e67c7971 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/IgtimiAccountsPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/IgtimiAccountsPanel.java @@ -35,6 +35,7 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.ImagesBarCell; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.IconResources; import com.sap.sse.gwt.client.dialog.DataEntryDialog; import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; @@ -48,7 +49,6 @@ public class IgtimiAccountsPanel extends FlowPanel { public static class AccountImagesBarCell extends ImagesBarCell { public static final String ACTION_REMOVE = "ACTION_REMOVE"; - private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); private final StringMessages stringMessages; public AccountImagesBarCell(StringMessages stringMessages) { @@ -64,7 +64,7 @@ public class IgtimiAccountsPanel extends FlowPanel { @Override protected Iterable getImageSpecs() { return Arrays.asList( - new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(resources.removeIcon()))); + new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(IconResources.INSTANCE.removeIcon()))); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImportFixesAndAddMappingsDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImportFixesAndAddMappingsDialog.java index b86541d42a9..03db328db33 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImportFixesAndAddMappingsDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImportFixesAndAddMappingsDialog.java @@ -60,8 +60,10 @@ public class ImportFixesAndAddMappingsDialog extends DataEntryDialog(sailingService, stringMessages, errorReporter, - new SingleSelectionModel(), true); + final Class selectionModelType = SingleSelectionModel.class; + @SuppressWarnings("unchecked") + final Class> singleSelectionType = (Class>) selectionModelType; + competitorTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, singleSelectionType, true); markTable = new MarkTableWrapper>( new SingleSelectionModel(), sailingService, stringMessages, errorReporter); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ItemToMapToDeviceSelectionPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ItemToMapToDeviceSelectionPanel.java index 2941c6b4758..61dd9736f6c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ItemToMapToDeviceSelectionPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ItemToMapToDeviceSelectionPanel.java @@ -35,9 +35,11 @@ public class ItemToMapToDeviceSelectionPanel implements IsWidget { this.selected = selected; this.errorReporter = errorReporter; + final Class selectionModelType = SingleSelectionModel.class; + @SuppressWarnings("unchecked") + final Class> singleSelectionType = (Class>) selectionModelType; + competitorTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, singleSelectionType, true); final SingleSelectionModel markSelectionModel = new SingleSelectionModel(); - competitorTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, - new SingleSelectionModel(), true); markTable = new MarkTableWrapper>(markSelectionModel, sailingService, stringMessages, errorReporter); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigImagesBarCell.java index f83621faf1a..7778bfe95ff 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigImagesBarCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigImagesBarCell.java @@ -6,6 +6,7 @@ import com.google.gwt.core.client.GWT; import com.google.gwt.text.shared.SafeHtmlRenderer; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.ImagesBarCell; +import com.sap.sse.gwt.client.IconResources; public class LeaderboardConfigImagesBarCell extends ImagesBarCell { public static final String ACTION_REMOVE = "ACTION_REMOVE"; @@ -35,7 +36,7 @@ public class LeaderboardConfigImagesBarCell extends ImagesBarCell { new ImageSpec(ACTION_EDIT_SCORES, stringMessages.actionEditScores(), makeImagePrototype(resources.scoresIcon())), new ImageSpec(ACTION_EDIT_COMPETITORS, stringMessages.actionEditCompetitors(), makeImagePrototype(resources.competitorsIcon())), new ImageSpec(ACTION_CONFIGURE_URL, stringMessages.actionConfigureUrl(), makeImagePrototype(resources.settingsActionIcon())), - new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(resources.removeIcon())), + new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(IconResources.INSTANCE.removeIcon())), new ImageSpec(ACTION_EXPORT_XML, stringMessages.actionExportXML(), makeImagePrototype(resources.exportXMLIcon())), new ImageSpec(ACTION_OPEN_COACH_DASHBOARD, stringMessages.actionOpenDashboard(), makeImagePrototype(resources.openCoachDashboard()))); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigImagesBarCell.java index 8e639715f3b..9f4528eda9a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigImagesBarCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigImagesBarCell.java @@ -6,6 +6,7 @@ import com.google.gwt.core.client.GWT; import com.google.gwt.text.shared.SafeHtmlRenderer; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.ImagesBarCell; +import com.sap.sse.gwt.client.IconResources; public class LeaderboardGroupConfigImagesBarCell extends ImagesBarCell { public static final String ACTION_REMOVE = "ACTION_REMOVE"; @@ -26,6 +27,6 @@ public class LeaderboardGroupConfigImagesBarCell extends ImagesBarCell { @Override protected Iterable getImageSpecs() { return Arrays.asList(new ImageSpec(ACTION_EDIT, stringMessages.actionEdit(), makeImagePrototype(resources.editIcon())), - new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(resources.removeIcon()))); + new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(IconResources.INSTANCE.removeIcon()))); } } \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java index 3f8684ea062..c8dfe53f38d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java @@ -44,6 +44,7 @@ import com.sap.sailing.gwt.ui.client.LeaderboardsRefresher; import com.sap.sailing.gwt.ui.client.RegattaRefresher; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn; import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter; import com.sap.sailing.gwt.ui.raceboard.RaceBoardViewConfiguration; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; @@ -63,6 +64,8 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements SafeHtml cell(String url, String displayName); } + private static AdminConsoleTableResources tableResources = GWT.create(AdminConsoleTableResources.class); + private static final AnchorTemplates ANCHORTEMPLATE = GWT.create(AnchorTemplates.class); private VerticalPanel mainPanel; @@ -186,6 +189,16 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements ListHandler leaderboardsListHandler = new ListHandler(leaderboardsProvider.getList()); leaderboardsTable = new CellTable(10000, tableRes); leaderboardsTable.ensureDebugId("LeaderboardsCellTable"); + SelectionCheckboxColumn leaderboardTableSelectionColumn = + new SelectionCheckboxColumn( + tableResources.cellTableStyle().cellTableCheckboxSelected(), tableResources.cellTableStyle().cellTableCheckboxDeselected(), tableResources.cellTableStyle().cellTableCheckboxColumnCell()) { + @Override + protected ListDataProvider getListDataProvider() { + return leaderboardsProvider; + } + }; + leaderboardsSelectionModel = leaderboardTableSelectionColumn.getSelectionModel(); + leaderboardsTable.setSelectionModel(leaderboardsSelectionModel, leaderboardTableSelectionColumn.getSelectionManager()); leaderboardsFilterablePanel = new LabeledAbstractFilterablePanel(filterLeaderboardsLabel, availableLeaderboards, leaderboardsTable, leaderboardsProvider) { @Override public Iterable getSearchableStrings(StrippedLeaderboardDTO t) { @@ -240,10 +253,10 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements leaderboardsTable.setWidth("100%"); leaderboardsTable.addColumnSortHandler(leaderboardsListHandler); + leaderboardsTable.addColumn(leaderboardTableSelectionColumn, leaderboardTableSelectionColumn.getHeader()); leaderboardsTable.addColumn(leaderboardsNameColumn, stringMessages.leaderboardName()); leaderboardsTable.addColumn(leaderboardsRacesColumn, stringMessages.races()); - leaderboardsSelectionModel = new MultiSelectionModel(); leaderboardsSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { @Override public void onSelectionChange(SelectionChangeEvent event) { @@ -251,11 +264,8 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements moveToGroupButton.setEnabled(selectedLeaderboards != null && selectedLeaderboards.size() > 0); } }); - leaderboardsTable.setSelectionModel(leaderboardsSelectionModel); - leaderboardsProvider.addDataDisplay(leaderboardsTable); leaderboardsPanel.add(leaderboardsTable); - return leaderboardsCaptionPanel; } @@ -350,11 +360,23 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements groupDetailsTable = new CellTable(10000, tableRes); groupDetailsTable.ensureDebugId("LeaderboardGroupsCellTable"); + groupDetailsProvider = new ListDataProvider(); + groupDetailsProvider.addDataDisplay(groupDetailsTable); + SelectionCheckboxColumn groupDetailsTableSelectionColumn = + new SelectionCheckboxColumn( + tableResources.cellTableStyle().cellTableCheckboxSelected(), tableResources.cellTableStyle().cellTableCheckboxDeselected(), tableResources.cellTableStyle().cellTableCheckboxColumnCell()) { + @Override + protected ListDataProvider getListDataProvider() { + return groupDetailsProvider; + } + }; + groupDetailsTable.setWidth("100%"); + groupDetailsTable.addColumn(groupDetailsTableSelectionColumn, groupDetailsTableSelectionColumn.getHeader()); groupDetailsTable.addColumn(groupDetailsNameColumn, stringMessages.leaderboardName()); groupDetailsTable.addColumn(groupDetailsRacesColumn, stringMessages.races()); - groupDetailsSelectionModel = new MultiSelectionModel(); + groupDetailsSelectionModel = groupDetailsTableSelectionColumn.getSelectionModel(); groupDetailsSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { @Override public void onSelectionChange(SelectionChangeEvent event) { @@ -364,10 +386,7 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements leaderboardUpButton.setEnabled(selectedLeaderboardsInGroup != null && selectedLeaderboardsInGroup.size() == 1); } }); - groupDetailsTable.setSelectionModel(groupDetailsSelectionModel); - - groupDetailsProvider = new ListDataProvider(); - groupDetailsProvider.addDataDisplay(groupDetailsTable); + groupDetailsTable.setSelectionModel(groupDetailsSelectionModel, groupDetailsTableSelectionColumn.getSelectionManager()); groupDetailsPanel.add(groupDetailsTable); //Create details functionality diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardRaceConfigImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardRaceConfigImagesBarCell.java index 3a938222b06..f37d691c789 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardRaceConfigImagesBarCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardRaceConfigImagesBarCell.java @@ -8,6 +8,7 @@ import com.sap.sailing.domain.common.LeaderboardType; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.ImagesBarCell; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; +import com.sap.sse.gwt.client.IconResources; public class LeaderboardRaceConfigImagesBarCell extends ImagesBarCell { public final static String ACTION_REMOVE = "ACTION_REMOVE"; @@ -35,7 +36,7 @@ public class LeaderboardRaceConfigImagesBarCell extends ImagesBarCell { StrippedLeaderboardDTO selectedLeaderboard = selectedLeaderboardProvider.getSelectedLeaderboard(); if (selectedLeaderboard != null && selectedLeaderboard.type != LeaderboardType.RegattaLeaderboard && selectedLeaderboard.type != LeaderboardType.RegattaMetaLeaderboard) { // race columns cannot be removed from a regatta leaderboard; they need to be removed from the regatta instead - result.add(new ImageSpec(ACTION_REMOVE, stringMessages.actionRaceRemove(), makeImagePrototype(resources.removeIcon()))); + result.add(new ImageSpec(ACTION_REMOVE, stringMessages.actionRaceRemove(), makeImagePrototype(IconResources.INSTANCE.removeIcon()))); } result.add(new ImageSpec(ACTION_REFRESH_RACELOG, stringMessages.refreshRaceLog(), makeImagePrototype(resources.reloadIcon()))); result.add(new ImageSpec(ACTION_SET_STARTTIME, stringMessages.setStartTime(), makeImagePrototype(resources.clockIcon()))); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaActionBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaActionBarCell.java index e5542f1aec9..cd2ef26340d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaActionBarCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaActionBarCell.java @@ -2,14 +2,13 @@ package com.sap.sailing.gwt.ui.adminconsole; import java.util.Arrays; -import com.google.gwt.core.client.GWT; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.ImagesBarCell; +import com.sap.sse.gwt.client.IconResources; public class MediaActionBarCell extends ImagesBarCell { public static final String ACTION_REMOVE = "ACTION_REMOVE"; - private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); private final StringMessages stringMessages; public MediaActionBarCell(StringMessages stringMessages) { @@ -20,6 +19,6 @@ public class MediaActionBarCell extends ImagesBarCell { @Override protected Iterable getImageSpecs() { return Arrays.asList(new ImageSpec(ACTION_REMOVE, - stringMessages.actionRemove(), makeImagePrototype(resources.removeIcon()))); + stringMessages.actionRemove(), makeImagePrototype(IconResources.INSTANCE.removeIcon()))); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingCompetitorRegistrationsDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingCompetitorRegistrationsDialog.java index 80ddf34d978..7249edae979 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingCompetitorRegistrationsDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingCompetitorRegistrationsDialog.java @@ -88,10 +88,11 @@ public class RaceLogTrackingCompetitorRegistrationsDialog extends AbstractSaveDi mainPanel.add(panel); CaptionPanel allCompetitorsPanel = new CaptionPanel(stringMessages.competitorPool()); CaptionPanel registeredCompetitorsPanel = new CaptionPanel(stringMessages.registeredCompetitors()); - allCompetitorsTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, - new MultiSelectionModel(), true); - registeredCompetitorsTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, - new MultiSelectionModel(), true); + final Class selectionModelType = MultiSelectionModel.class; + @SuppressWarnings("unchecked") + final Class> multiSelectionType = (Class>) selectionModelType; + allCompetitorsTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, multiSelectionType, true); + registeredCompetitorsTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, multiSelectionType, true); allCompetitorsPanel.add(allCompetitorsTable); registeredCompetitorsPanel.add(registeredCompetitorsTable); VerticalPanel movePanel = new VerticalPanel(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingDeviceMappingsImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingDeviceMappingsImagesBarCell.java index d31582aa965..2cc7ce92ca8 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingDeviceMappingsImagesBarCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingDeviceMappingsImagesBarCell.java @@ -6,6 +6,7 @@ import com.google.gwt.core.client.GWT; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.ImagesBarCell; import com.sap.sailing.gwt.ui.shared.DeviceMappingDTO; +import com.sap.sse.gwt.client.IconResources; public class RaceLogTrackingDeviceMappingsImagesBarCell extends ImagesBarCell { public static final String ACTION_CLOSE = "CLOSE"; @@ -23,7 +24,7 @@ public class RaceLogTrackingDeviceMappingsImagesBarCell extends ImagesBarCell { ArrayList result = new ArrayList(); DeviceMappingDTO mapping = (DeviceMappingDTO) getContext().getKey(); - result.add(new ImageSpec(ACTION_REMOVE, stringMessages.remove(), makeImagePrototype(resources.removeIcon()))); + result.add(new ImageSpec(ACTION_REMOVE, stringMessages.remove(), makeImagePrototype(IconResources.INSTANCE.removeIcon()))); if (mapping.from == null || mapping.to == null) { result.add(new ImageSpec(ACTION_CLOSE, stringMessages.closeTimeRange(), diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaConfigImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaConfigImagesBarCell.java index 5ea041d561e..2c24f547e1a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaConfigImagesBarCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaConfigImagesBarCell.java @@ -6,6 +6,7 @@ import com.google.gwt.core.client.GWT; import com.google.gwt.text.shared.SafeHtmlRenderer; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.ImagesBarCell; +import com.sap.sse.gwt.client.IconResources; public class RegattaConfigImagesBarCell extends ImagesBarCell { static final String ACTION_REMOVE = "ACTION_REMOVE"; @@ -26,6 +27,6 @@ public class RegattaConfigImagesBarCell extends ImagesBarCell { @Override protected Iterable getImageSpecs() { return Arrays.asList(new ImageSpec(ACTION_EDIT, stringMessages.actionEdit(), makeImagePrototype(resources.editIcon())), - new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(resources.removeIcon()))); + new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(IconResources.INSTANCE.removeIcon()))); } } \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaWithSeriesAndFleetsDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaWithSeriesAndFleetsDialog.java index 0ff24e805ba..bd855d46bea 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaWithSeriesAndFleetsDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaWithSeriesAndFleetsDialog.java @@ -2,7 +2,6 @@ package com.sap.sailing.gwt.ui.adminconsole; import java.util.List; -import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.ui.Grid; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.TextBox; @@ -12,10 +11,10 @@ import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.SeriesDTO; +import com.sap.sse.gwt.client.IconResources; import com.sap.sse.gwt.client.controls.listedit.ListEditorComposite; public abstract class RegattaWithSeriesAndFleetsDialog extends AbstractRegattaWithSeriesAndFleetsDialog { - private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); protected TextBox nameEntryField; protected TextBox boatClassEntryField; @@ -38,7 +37,7 @@ public abstract class RegattaWithSeriesAndFleetsDialog extends AbstractRegattaWi @Override protected ListEditorComposite createSeriesEditor(Iterable series) { - return new SeriesWithFleetsListEditor(series, stringMessages, resources.removeIcon(), isEnableFleetRemoval()); + return new SeriesWithFleetsListEditor(series, stringMessages, IconResources.INSTANCE.removeIcon(), isEnableFleetRemoval()); } protected abstract boolean isEnableFleetRemoval(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingAdminConsoleFeatures.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingAdminConsoleFeatures.java deleted file mode 100644 index 7d1fe18dafb..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingAdminConsoleFeatures.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.sap.sailing.gwt.ui.adminconsole; - -import com.sap.sailing.gwt.ui.usermanagement.UserRoles; -import com.sap.sse.gwt.adminconsole.AdminConsoleFeatures; - -public interface SailingAdminConsoleFeatures { - final AdminConsoleFeatures MANAGE_EVENTS = new AdminConsoleFeatures("MANAGE_EVENTS", UserRoles.eventmanager.getRolename(), UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_REGATTAS = new AdminConsoleFeatures("MANAGE_REGATTAS", UserRoles.eventmanager.getRolename(), UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_TRACKED_RACES = new AdminConsoleFeatures("MANAGE_TRACKED_RACES", UserRoles.eventmanager.getRolename(), UserRoles.administrator.getRolename()); - final AdminConsoleFeatures SHOW_TRACKED_RACES = new AdminConsoleFeatures("SHOW_TRACKED_RACES", UserRoles.eventmanager.getRolename(), UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_RACELOG_TRACKING = new AdminConsoleFeatures("MANAGE_RACELOG_TRACKING", UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_ALL_COMPETITORS = new AdminConsoleFeatures("MANAGE_ALL_COMPETITORS", UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_COURSE_LAYOUT = new AdminConsoleFeatures("MANAGE_COURSE_LAYOUT", UserRoles.eventmanager.getRolename(), UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_WIND = new AdminConsoleFeatures("MANAGE_WIND", UserRoles.eventmanager.getRolename(), UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_IGTIMI_ACCOUNTS = new AdminConsoleFeatures("MANAGE_IGTIMI_ACCOUNTS", UserRoles.eventmanager.getRolename(), UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_LEADERBOARDS = new AdminConsoleFeatures("MANAGE_LEADERBOARDS", UserRoles.eventmanager.getRolename(), UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_LEADERBOARD_GROUPS = new AdminConsoleFeatures("MANAGE_LEADERBOARD_GROUPS", UserRoles.eventmanager.getRolename(), UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_RESULT_IMPORT_URLS = new AdminConsoleFeatures("MANAGE_RESULT_IMPORT_URLS", UserRoles.eventmanager.getRolename(), UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_STRUCTURE_IMPORT_URLS = new AdminConsoleFeatures("MANAGE_STRUCTURE_IMPORT_URLS", UserRoles.eventmanager.getRolename(), UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_MEDIA = new AdminConsoleFeatures("MANAGE_MEDIA", UserRoles.eventmanager.getRolename(), UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_SAILING_SERVER_INSTANCES = new AdminConsoleFeatures("MANAGE_SAILING_SERVER_INSTANCES", UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_REPLICATION = new AdminConsoleFeatures("MANAGE_REPLICATION", UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_MASTERDATA_IMPORT = new AdminConsoleFeatures("MANAGE_MASTERDATA_IMPORT", UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_DEVICE_CONFIGURATION = new AdminConsoleFeatures("MANAGE_DEVICE_CONFIGURATION", UserRoles.eventmanager.getRolename(), UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_USERS = new AdminConsoleFeatures("MANAGE_USERS", UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_FILE_STORAGE = new AdminConsoleFeatures("MANAGE_FILE_STORAGE", UserRoles.administrator.getRolename()); -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesConfigImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesConfigImagesBarCell.java index 839416b3260..2f1263e5921 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesConfigImagesBarCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesConfigImagesBarCell.java @@ -6,6 +6,7 @@ import com.google.gwt.core.client.GWT; import com.google.gwt.text.shared.SafeHtmlRenderer; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.ImagesBarCell; +import com.sap.sse.gwt.client.IconResources; public class SeriesConfigImagesBarCell extends ImagesBarCell { static final String ACTION_REMOVE = "ACTION_REMOVE"; @@ -26,6 +27,6 @@ public class SeriesConfigImagesBarCell extends ImagesBarCell { @Override protected Iterable getImageSpecs() { return Arrays.asList(new ImageSpec(ACTION_EDIT, stringMessages.actionEdit(), makeImagePrototype(resources.editIcon())), - new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(resources.removeIcon()))); + new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(IconResources.INSTANCE.removeIcon()))); } } \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java index 68b306e0a3e..37c769e6c8a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java @@ -6,7 +6,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import com.google.gwt.core.shared.GWT; import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.event.dom.client.ClickEvent; @@ -31,14 +30,12 @@ import com.sap.sailing.domain.common.dto.RaceColumnInSeriesDTO; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.SeriesDTO; +import com.sap.sse.gwt.client.IconResources; import com.sap.sse.gwt.client.controls.listedit.StringListEditorComposite; import com.sap.sse.gwt.client.controls.listedit.StringListInlineEditorComposite; import com.sap.sse.gwt.client.dialog.DataEntryDialog; public class SeriesEditDialog extends DataEntryDialog { - - private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); - private TextBox seriesNameTextBox; private CheckBox isMedalCheckbox; private CheckBox startWithZeroScoreCheckbox; @@ -204,7 +201,7 @@ public class SeriesEditDialog extends DataEntryDialog { discardThresholdBoxesWidget.setVisible(useSeriesResultDiscardingThresholdsCheckbox.getValue()); additionalWidgetPanel.add(discardThresholdBoxesWidget); - raceNamesEditor = new StringListInlineEditorComposite(getExistingRacesOfSeries(), new RaceNamesEditorUi(regatta, stringMessages, resources.removeIcon(), seriesName)); + raceNamesEditor = new StringListInlineEditorComposite(getExistingRacesOfSeries(), new RaceNamesEditorUi(regatta, stringMessages, IconResources.INSTANCE.removeIcon(), seriesName)); raceNamesEditor.ensureDebugId("RaceNamesStringListEditorComposite"); raceNamesEditor.addValueChangeHandler(new ValueChangeHandler>() { @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesWithFleetsCreateDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesWithFleetsCreateDialog.java index dd5031d3b76..7343855a644 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesWithFleetsCreateDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesWithFleetsCreateDialog.java @@ -20,6 +20,7 @@ import com.sap.sailing.domain.common.LeaderboardNameConstants; import com.sap.sailing.domain.common.dto.FleetDTO; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.SeriesDTO; +import com.sap.sse.gwt.client.IconResources; import com.sap.sse.gwt.client.controls.listedit.ListEditorComposite; import com.sap.sse.gwt.client.dialog.DataEntryDialog; @@ -146,7 +147,7 @@ public class SeriesWithFleetsCreateDialog extends DataEntryDialog { protected void initializeFleetListComposite(StringMessages stringMessages) { fleetListComposite = new FleetListEditorComposite(Arrays.asList(new FleetDTO( - LeaderboardNameConstants.DEFAULT_FLEET_NAME, 0, null)), stringMessages, resources.removeIcon()); + LeaderboardNameConstants.DEFAULT_FLEET_NAME, 0, null)), stringMessages, IconResources.INSTANCE.removeIcon()); fleetListComposite.ensureDebugId("FleetListEditorComposite"); fleetListComposite.addValueChangeHandler(new ValueChangeHandler>() { @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesWithFleetsDefaultCreateDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesWithFleetsDefaultCreateDialog.java index 12c7011cd36..2ba1ac26a8b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesWithFleetsDefaultCreateDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesWithFleetsDefaultCreateDialog.java @@ -12,6 +12,7 @@ import com.sap.sailing.domain.common.LeaderboardNameConstants; import com.sap.sailing.domain.common.dto.FleetDTO; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.SeriesDTO; +import com.sap.sse.gwt.client.IconResources; /** * A variant of the dialog used to create a series within a regatta that has fleets. This variant is actually more of a @@ -49,7 +50,7 @@ public class SeriesWithFleetsDefaultCreateDialog extends SeriesWithFleetsCreateD protected void initializeFleetListComposite(StringMessages stringMessages) { fleetListComposite = new FleetListEditableEditorComposite(Arrays.asList(new FleetDTO( - LeaderboardNameConstants.DEFAULT_FLEET_NAME, 0, null)), stringMessages, resources.removeIcon()); + LeaderboardNameConstants.DEFAULT_FLEET_NAME, 0, null)), stringMessages, IconResources.INSTANCE.removeIcon()); fleetListComposite.ensureDebugId("FleetListEditableEditorComposite"); fleetListComposite.addValueChangeHandler(new ValueChangeHandler>() { @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TableWrapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TableWrapper.java index 9be6496a3a4..9cac31faafd 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TableWrapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TableWrapper.java @@ -19,7 +19,7 @@ import com.sap.sse.gwt.client.ErrorReporter; */ public abstract class TableWrapper> implements IsWidget { protected final CellTable table; - protected final S selectionModel; + private final S selectionModel; protected final ListDataProvider dataProvider; protected VerticalPanel mainPanel; protected final SailingServiceAsync sailingService; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/GwtUrlHelper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/GwtUrlHelper.java new file mode 100644 index 00000000000..58c0c4ce99d --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/GwtUrlHelper.java @@ -0,0 +1,22 @@ +package com.sap.sailing.gwt.ui.client; + +import com.google.gwt.core.shared.GWT; +import com.google.gwt.http.client.URL; +import com.sap.sse.common.util.UrlHelper; + +/** + * Tycho complains that {@link com.google.gwt} cannot be resolved when placing this in *common projects, where it would ideally + * go (then we could also auto-determine if GWT context is present, e.g. through {@link GWT#isClient}). + * + * @author Fredrik Teschke + * + */ +public enum GwtUrlHelper implements UrlHelper { + INSTANCE; + + @Override + public String encodeUrl(String decodedUrlString) { + return URL.encode(decodedUrlString); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java index 938a6aa1123..46ca93562b8 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java @@ -87,6 +87,7 @@ import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO; import com.sap.sse.common.NoCorrespondingServiceRegisteredException; import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.mail.MailException; import com.sap.sse.common.search.KeywordQuery; import com.sap.sse.gwt.client.filestorage.FileStorageManagementGwtService; @@ -413,7 +414,7 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS */ Iterable getCompetitorsOfLeaderboard(String leaderboardName, boolean lookInRaceLogs); - CompetitorDTO addOrUpdateCompetitor(CompetitorDTO competitor); + CompetitorDTO addOrUpdateCompetitor(CompetitorDTO competitor) throws Exception; void allowCompetitorResetToDefaults(Iterable competitors); @@ -573,4 +574,7 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS List getDeviceMappingsFromLogHierarchy(String leaderboardName, String raceColumnName, String fleetName) throws TransformationException; + + void inviteCompetitorsForTrackingViaEmail(String serverUrlWithoutTrailingSlash, EventDTO event, + String leaderboardName, Set competitors, String localeInfo) throws MailException; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java index 196080e488d..90c914384a3 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java @@ -684,4 +684,8 @@ public interface SailingServiceAsync extends BuildVersionRetriever, FileStorageM void createRegattaStructure(Iterable regattaNames, EventDTO newEvent, AsyncCallback asyncCallback); + void getActiveFileStorageServiceName(AsyncCallback callback); + + void inviteCompetitorsForTrackingViaEmail(String serverUrlWithoutTrailingSlash, EventDTO event, + String leaderboardName, Set competitors, String localeInfo, AsyncCallback callback); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index cbef88b3ee3..3e6a57c1672 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java @@ -1,8 +1,9 @@ package com.sap.sailing.gwt.ui.client; import com.google.gwt.core.client.GWT; +import com.google.gwt.json.client.JSONValue; -public interface StringMessages extends com.sap.sse.gwt.client.StringMessages { +public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, com.sap.sse.gwt.adminconsole.StringMessages { public static final StringMessages INSTANCE = GWT.create(StringMessages.class); String tracTracEvents(); @@ -34,6 +35,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages { String carry(); String competitor(); String competitors(); + String competitorsSelected(int selectedCompetitorsCount, int competitorsCount); String defaultLeaderboard(); String noSuchLeaderboard(); String leaderboardConfiguration(); @@ -1141,4 +1143,18 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages { String scoringSchemeHighPointEssOverallDescription(); String timeBetweenRaceStartAndCompetitorStartInSeconds(); String timeBetweenRaceStartAndCompetitorStartInSecondsTooltip(); + String email(); + String pleaseEnterAnEmail(); + String inviteCompetitors(); + String notAllCompetitorsProvideEmail(); + String sendingMailsFailed(); + String sendingMailsSuccessfull(); + String selectEventForInvitation(); + String imageURL(); + String upload(); + String removeResult(JSONValue status, JSONValue message); + String uploadSuccessful(); + String fileUploadResult(JSONValue status, JSONValue message); + String showCompetitorSailIdColumn(); + String showCompetitorFullNameColumn(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index 6a71c26d303..38655ee6531 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -29,6 +29,7 @@ total=\u03A3 carry=Accumulated competitor=Competitor competitors=Competitors +competitorsSelected={0}/{1} competitors selected. defaultLeaderboard=Default Leaderboard noSuchLeaderboard=No such leaderboard leaderboardConfiguration=Leaderboard Configuration @@ -1121,4 +1122,18 @@ fileStorage=File Storage active=Active scoringSchemeHighPointEssOverallDescription=Score in points. Winner of an act scores 10 points, 2nd - 9 points, .... Any tie in the overall Extreme Sailing Series is broken in favour of the competitor with the most Act wins. If that does not break the tie, the result in the last Act will be used. timeBetweenRaceStartAndCompetitorStartInSeconds=Start delay -timeBetweenRaceStartAndCompetitorStartInSecondsTooltip=Time between the start of the race and the time the competitor passed the start waypoint. Negative time suggests OCS. \ No newline at end of file +timeBetweenRaceStartAndCompetitorStartInSecondsTooltip=Time between the start of the race and the time the competitor passed the start waypoint. Negative time suggests OCS. +email=E-Mail +pleaseEnterAnEmail=Please enter an E-Mail. +inviteCompetitors=Invite competitors +notAllCompetitorsProvideEmail=Not all competitors provide an E-Mail +sendingMailsFailed=Sending Mails failed +sendingMailsSuccessfull=Successfully sent E-Mails +selectEventForInvitation=Select Event for Invitation +imageURL=Image URL +upload=Upload +removeResult=Remove operation: {0} {1} +uploadSuccessful=Upload successful +fileUploadResult=File upload: {0} {1}.\nIn case of error please check that your File Storage service is properly configured. +showCompetitorSailIdColumn=Show Competitor Sail Id +showCompetitorFullNameColumn=Show Competitor Full Name diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties index 5fc7d30cba1..3890017f3c1 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties @@ -29,6 +29,7 @@ total=Gesamt carry=übertr. competitor=Team competitors=Teams +competitorsSelected={0}/{1} Teams ausgewählt. defaultLeaderboard=Alle Ergebnisse noSuchLeaderboard=Rangliste nicht gefunden leaderboardConfiguration=Ranglisten-Konfiguration @@ -1111,4 +1112,18 @@ fileStorage=Datei Speicherung active=Aktiv scoringSchemeHighPointEssOverallDescription=High Point System, der Gewinner eines Acts erhält 10 Punkte, der Zweite 9 und so weiter. timeBetweenRaceStartAndCompetitorStartInSeconds=Startverzug -timeBetweenRaceStartAndCompetitorStartInSecondsTooltip=Zeit seit dem Start der Wettfahrt, nach der der Teilnehmer die Startmarke passiert hat. Negative Werte suggerieren einen Frühstart. \ No newline at end of file +timeBetweenRaceStartAndCompetitorStartInSecondsTooltip=Zeit seit dem Start der Wettfahrt, nach der der Teilnehmer die Startmarke passiert hat. Negative Werte suggerieren einen Frühstart. +email=E-Mail +pleaseEnterAnEmail=Bitte geben Sie eine E-Mail ein. +inviteCompetitors=Teilnehmer einladen +notAllCompetitorsProvideEmail=Nicht alle Teilnehmer haben eine E-Mail angegeben +sendingMailsFailed=Versenden der E-Mails fehlgeschlagen +sendingMailsSuccessfull=Versenden der E-Mails war erfolgreich! +selectEventForInvitation=Event für Einladung auswählen +imageURL=Bild-URL +upload=Hochladen +removeResult=Löschergebnis: {0} {1} +uploadSuccessful=Hochladen erfolgreich +fileUploadResult=Ergebnis des Hochladens: {0} {1}\nIm Fehlerfall bitte die Konfiguration für "Datei Speicherung" prüfen. +showCompetitorSailIdColumn=Sail Id anzeigen +showCompetitorFullNameColumn=Vollen Namen anzeigen diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties index 67d2e6e8c6d..8302679bb69 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties @@ -29,6 +29,7 @@ total=\u03A3 carry=Суммарная competitor=Участник competitors=Участники +competitorsSelected={0}/{1} Участники. defaultLeaderboard=Таблица лидеров по умолчанию noSuchLeaderboard=Такая таблица лидеров отсутствует leaderboardConfiguration=Форма таблицы лидеров @@ -1060,4 +1061,18 @@ fileStorage=File Storage active=Active scoringSchemeHighPointEssOverallDescription=Score in points. Winner of an act scores 10 points, 2nd - 9 points, .... Any tie in the overall Extreme Sailing Series is broken in favour of the competitor with the most Act wins. If that does not break the tie, the result in the last Act will be used. timeBetweenRaceStartAndCompetitorStartInSeconds=Start delay -timeBetweenRaceStartAndCompetitorStartInSecondsTooltip=Time between the start of the race and the time the competitor passed the start waypoint. Negative time suggests OCS. \ No newline at end of file +timeBetweenRaceStartAndCompetitorStartInSecondsTooltip=Time between the start of the race and the time the competitor passed the start waypoint. Negative time suggests OCS. +email=эмаль +pleaseEnterAnEmail=Пожалуйста, введите эмаль +inviteCompetitors=приглашать Участники +notAllCompetitorsProvideEmail=Not all competitors provide an E-Mail +sendingMailsFailed=Sending Mails failed +sendingMailsSuccessfull=Successfully sent E-Mails +selectEventForInvitation=Select Event for Invitation +imageURL=Image URL +upload=Upload +removeResult=Remove operation: {0} {1} +uploadSuccessful=Upload successful +fileUploadResult=File upload: {0} {1}.\nIn case of error please check that your File Storage service is properly configured. +showCompetitorSailIdColumn=Show Competitor Sail Id +showCompetitorFullNameColumn=Show Competitor Full Name diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaPlayerManagerComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaPlayerManagerComponent.java index b38c75cdfd1..cc2803d896c 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaPlayerManagerComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaPlayerManagerComponent.java @@ -25,6 +25,7 @@ import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.media.MediaTrack; import com.sap.sailing.domain.common.media.MediaTrack.MediaType; import com.sap.sailing.domain.common.media.MediaTrack.Status; +import com.sap.sailing.domain.common.security.Roles; import com.sap.sailing.gwt.ui.client.MediaServiceAsync; import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider; import com.sap.sailing.gwt.ui.client.StringMessages; @@ -37,7 +38,6 @@ import com.sap.sailing.gwt.ui.client.media.shared.VideoPlayer; import com.sap.sailing.gwt.ui.client.media.shared.VideoSynchPlayer; import com.sap.sailing.gwt.ui.client.shared.components.Component; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; -import com.sap.sailing.gwt.ui.usermanagement.UserRoles; import com.sap.sse.common.TimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.gwt.client.ErrorReporter; @@ -49,6 +49,7 @@ import com.sap.sse.gwt.client.player.Timer.PlayModes; import com.sap.sse.gwt.client.player.Timer.PlayStates; import com.sap.sse.gwt.client.useragent.UserAgentDetails; import com.sap.sse.gwt.client.useragent.UserAgentDetails.AgentTypes; +import com.sap.sse.security.shared.DefaultRoles; import com.sap.sse.security.ui.client.UserService; import com.sap.sse.security.ui.shared.UserDTO; @@ -478,8 +479,8 @@ public class MediaPlayerManagerComponent implements Component, PlayStateLi }; final VideoSynchPlayer videoPlayer; final UserDTO currentUser = userService.getCurrentUser(); - boolean showSynchControls = currentUser != null && (currentUser.hasRole(UserRoles.administrator.getRolename()) || - currentUser.hasRole(UserRoles.eventmanager.getRolename()) || currentUser.hasRole(UserRoles.mediaeditor.getRolename())); + boolean showSynchControls = currentUser != null && (currentUser.hasRole(DefaultRoles.ADMIN.getRolename()) || + currentUser.hasRole(Roles.eventmanager.getRolename()) || currentUser.hasRole(Roles.mediaeditor.getRolename())); if (videoTrack.isYoutube()) { videoPlayer = new VideoYoutubePlayer(videoTrack, getRaceStartTime(), showSynchControls, raceTimer); } else { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java index feb71eaee46..c6899dd8751 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java @@ -27,6 +27,7 @@ import org.moxieapps.gwt.highcharts.client.plotOptions.Marker; import org.moxieapps.gwt.highcharts.client.plotOptions.Marker.Symbol; import com.google.gwt.i18n.client.NumberFormat; +import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.RequiresResize; import com.google.gwt.user.client.ui.Widget; @@ -180,7 +181,7 @@ public abstract class AbstractCompetitorLeaderboardChart extends A })); } - protected void clearChart() { + public void clearChart() { competitorSeries.clear(); chart.removeAllSeries(); } @@ -245,7 +246,9 @@ public abstract class AbstractCompetitorLeaderboardChart extends A break; } - chart.setSizeToMatchContainer(); + // TODO will removing the following line do harm on any usage of this abstract base class? + // chart.setSizeToMatchContainer(); + // it's important here to recall the redraw method, otherwise the bug fix for wrong checkbox // positions (nativeAdjustCheckboxPosition) // in the BaseChart class would not be called @@ -296,8 +299,11 @@ public abstract class AbstractCompetitorLeaderboardChart extends A raceColumnNumber++; } } - chart.getYAxis().setMaxPadding(0.5 / maxCompetitorCount).setMinPadding(0.5 / maxCompetitorCount); - chart.setHeight(maxCompetitorCount * 30 + 100 + "px"); + setHeight(); + } + + private void setHeight() { + chart.setSize(chart.getOffsetWidth(), Window.getClientHeight()); } private void fillTotalPointsSeries( @@ -341,8 +347,7 @@ public abstract class AbstractCompetitorLeaderboardChart extends A raceColumnNumber++; } } - chart.getYAxis().setMaxPadding(0.5/maxCompetitorCount).setMinPadding(0.5/maxCompetitorCount); - chart.setHeight(maxCompetitorCount * 30 + 100 + "px"); + setHeight(); } private boolean hasValidValues(List values) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/SelectionCheckboxColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/SelectionCheckboxColumn.java index 95c93ef6037..d328719ca5e 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/SelectionCheckboxColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/SelectionCheckboxColumn.java @@ -130,10 +130,13 @@ public abstract class SelectionCheckboxColumn extends AbstractSortableColumnW } /** - * Subclass implementation has to tell whether the checkbox is selected or deselected for row + * The checkbox value is determined by by selection state of row, using the + * {@link #getSelectionModel() selection model}. */ @Override - public abstract Boolean getValue(T row); + public Boolean getValue(T row) { + return getSelectionModel().isSelected(row); + } /** * The default header display is a check mark. Subclasses may redefine this. diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java index 5220df45d3c..35019d4437b 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java @@ -214,10 +214,14 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint { if (parameterMap.containsKey(LeaderboardUrlSettings.PARAM_RACE_NAME) || parameterMap.containsKey(LeaderboardUrlSettings.PARAM_RACE_DETAIL) || parameterMap.containsKey(LeaderboardUrlSettings.PARAM_LEG_DETAIL) || parameterMap.containsKey(LeaderboardUrlSettings.PARAM_MANEUVER_DETAIL) || parameterMap.containsKey(LeaderboardUrlSettings.PARAM_OVERALL_DETAIL) || parameterMap.containsKey(LeaderboardUrlSettings.PARAM_SHOW_ADDED_SCORES) || - parameterMap.containsKey(LeaderboardUrlSettings.PARAM_SHOW_OVERALL_COLUMN_WITH_NUMBER_OF_RACES_COMPLETED)) { + parameterMap.containsKey(LeaderboardUrlSettings.PARAM_SHOW_OVERALL_COLUMN_WITH_NUMBER_OF_RACES_COMPLETED) + || parameterMap.containsKey(LeaderboardUrlSettings.PARAM_SHOW_COMPETITOR_NAME_COLUMNS)) { List maneuverDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_MANEUVER_DETAIL)); List raceDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_RACE_DETAIL)); List overallDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_OVERALL_DETAIL)); + if (overallDetails.isEmpty()) { + overallDetails = Collections.singletonList(DetailType.REGATTA_RANK); + } List legDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_LEG_DETAIL)); List namesOfRacesToShow = getStringListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_RACE_NAME)); boolean showAddedScores = parameterMap.containsKey(LeaderboardUrlSettings.PARAM_SHOW_ADDED_SCORES) ? @@ -227,13 +231,27 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint { boolean autoExpandPreSelectedRace = parameterMap.containsKey(LeaderboardUrlSettings.PARAM_AUTO_EXPAND_PRESELECTED_RACE) ? Boolean.valueOf(parameterMap.get(LeaderboardUrlSettings.PARAM_AUTO_EXPAND_PRESELECTED_RACE).get(0)) : (namesOfRacesToShow != null && namesOfRacesToShow.size() == 1); + boolean showCompetitorSailIdColumn = true; + boolean showCompetitorFullNameColumn = true; + if (parameterMap.containsKey(LeaderboardUrlSettings.PARAM_SHOW_COMPETITOR_NAME_COLUMNS)) { + String value = parameterMap.get(LeaderboardUrlSettings.PARAM_SHOW_COMPETITOR_NAME_COLUMNS).get(0); + if (value.equals(LeaderboardUrlSettings.COMPETITOR_NAME_COLUMN_FULL_NAME)) { + showCompetitorSailIdColumn = false; + } else if (value.equals(LeaderboardUrlSettings.COMPETITOR_NAME_COLUMN_SAIL_ID)) { + showCompetitorFullNameColumn = false; + } else if (value.trim().equals("")) { + showCompetitorFullNameColumn = false; + showCompetitorSailIdColumn = false; + } + } result = new LeaderboardSettings(maneuverDetails, legDetails, raceDetails, overallDetails, /* namesOfRaceColumnsToShow */ null, namesOfRacesToShow, numberOfLastRacesToShow, autoExpandPreSelectedRace, refreshIntervalMillis, /* sort by column */ (namesOfRacesToShow != null && !namesOfRacesToShow.isEmpty()) ? namesOfRacesToShow.get(0) : null, /* ascending */ true, /* updateUponPlayStateChange */ raceDetails.isEmpty() && legDetails.isEmpty(), - raceColumnSelectionStrategy, showAddedScores, showOverallColumnWithNumberOfRacesSailedPerCompetitor); + raceColumnSelectionStrategy, showAddedScores, showOverallColumnWithNumberOfRacesSailedPerCompetitor, + showCompetitorSailIdColumn, showCompetitorFullNameColumn); } else { final List overallDetails = Collections.singletonList(DetailType.REGATTA_RANK); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java index d0f2c412916..2e572b80fa9 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java @@ -225,6 +225,9 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS * but the cumulative score up to that race. */ private boolean showAddedScores; + + private boolean showCompetitorSailId; + private boolean showCompetitorFullName; /** * When true then an additional column just before the overall points is displayed that sums up @@ -395,6 +398,8 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS settingsUpdatedExplicitly = true; } setShowAddedScores(newSettings.isShowAddedScores()); + setShowCompetitorSailId(newSettings.isShowCompetitorSailIdColumn()); + setShowCompetitorFullName(newSettings.isShowCompetitorFullNameColumn()); setShowOverallColumnWithNumberOfRacesCompletedPerCompetitor(newSettings.isShowOverallColumnWithNumberOfRacesCompletedPerCompetitor()); final List> columnsToExpandAgain = new ArrayList>(); for (int i = 0; i < getLeaderboardTable().getColumnCount(); i++) { @@ -1734,6 +1739,8 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS leaderboardAsTableSelectionModelRegistration = leaderboardSelectionModel.addSelectionChangeHandler(selectionChangeHandler); leaderboardTable.setSelectionModel(leaderboardSelectionModel, selectionCheckboxColumn.getSelectionManager()); setShowAddedScores(settings.isShowAddedScores()); + setShowCompetitorSailId(settings.isShowCompetitorSailIdColumn()); + setShowCompetitorFullName(settings.isShowCompetitorFullNameColumn()); setShowOverallColumnWithNumberOfRacesCompletedPerCompetitor(settings.isShowOverallColumnWithNumberOfRacesCompletedPerCompetitor()); if (timer.isInitialized()) { loadCompleteLeaderboard(getLeaderboardDisplayDate()); @@ -2029,6 +2036,22 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS this.showAddedScores = showAddedScores; } + private boolean isShowCompetitorSailId() { + return showCompetitorSailId; + } + + private void setShowCompetitorSailId(boolean showCompetitorSailId) { + this.showCompetitorSailId = showCompetitorSailId; + } + + private boolean isShowCompetitorFullName() { + return showCompetitorFullName; + } + + private void setShowCompetitorFullName(boolean showCompetitorFullName) { + this.showCompetitorFullName = showCompetitorFullName; + } + /** * The time point for which the leaderboard currently shows results. In {@link PlayModes#Replay replay mode} this is * the {@link #timer}'s time point. In {@link PlayModes#Live live mode} the {@link #timer}'s time is quantized to @@ -2473,7 +2496,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS int columnIndex = 0; columnIndex = ensureSelectionCheckboxColumn(columnIndex); columnIndex = ensureRankColumn(columnIndex); - columnIndex = ensureSailIDAndCompetitorColumn(columnIndex); + columnIndex = ensureSailIDAndCompetitorColumn(); columnIndex = updateCarryColumn(leaderboard, columnIndex); adjustOverallDetailColumns(leaderboard, columnIndex); // first remove race columns no longer needed: @@ -2784,35 +2807,44 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS } /** - * Assumes that the sail ID and competitor column are mandatory elements of a leaderboard; hence, if the leaderboard - * has columns where sail ID and competitor names are expected, it is silently assumed that those are already in the - * correct place because these columns are never optional. - * - * @param sailIdColumnIndex - * the 0-based index where the sail ID column is to be placed; the competitor name column will be at the - * index greater by one - * @return the 0-based index for the next column; two greater than sailIdColumnIndex because this - * method will always make sure that two columns are placed in the layout, one at - * sailIdColumnIndex and one at sailIdColumnIndex+1. + * @return the 0-based index for the next column */ - private int ensureSailIDAndCompetitorColumn(int sailIdColumnIndex) { - final int nextColumnIndex; - if (getLeaderboardTable().getColumnCount() <= sailIdColumnIndex) { - addColumn(new SailIDColumn(new CompetitorFetcher() { - @Override - public CompetitorDTO getCompetitor(LeaderboardRowDTO t) { - return t.competitor; + private int ensureSailIDAndCompetitorColumn() { + SailIDColumn sailIdColumn = new SailIDColumn(new CompetitorFetcher() { + @Override + public CompetitorDTO getCompetitor(LeaderboardRowDTO t) { + return t.competitor; + } + }); + if (getLeaderboardTable().getColumnCount() >= 3) { // table already filled with columns + if (isShowCompetitorSailId()) { + if (!(getLeaderboardTable().getColumn(2) instanceof SailIDColumn)) { + insertColumn(2, sailIdColumn); } - })); - addColumn(createCompetitorColumn()); - } else { - if (!(getLeaderboardTable().getColumn(sailIdColumnIndex) instanceof SailIDColumn)) { - throw new RuntimeException("The second column must always be the sail ID column but it was of type " - + getLeaderboardTable().getColumn(sailIdColumnIndex).getClass().getName()); + } else { + if (getLeaderboardTable().getColumn(2) instanceof SailIDColumn) { + removeColumn(2); + } + } + final int competitorFullNameColumnIndex = 2 + (isShowCompetitorSailId() ? 1 : 0); + if (isShowCompetitorFullName()) { + if (!(getLeaderboardTable().getColumn(competitorFullNameColumnIndex) instanceof CompetitorColumn)) { + insertColumn(competitorFullNameColumnIndex, createCompetitorColumn()); + } + } else { + if (getLeaderboardTable().getColumn(competitorFullNameColumnIndex) instanceof CompetitorColumn) { + removeColumn(competitorFullNameColumnIndex); + } + } + } else { // table just being initialized + if (isShowCompetitorSailId()) { + addColumn(sailIdColumn); + } + if (isShowCompetitorFullName()) { + addColumn(createCompetitorColumn()); } } - nextColumnIndex = sailIdColumnIndex + 2; - return nextColumnIndex; + return (isShowRegattaRankColumn() ? 2 : 1) + (isShowCompetitorSailId() ? 1 : 0) + (isShowCompetitorFullName() ? 1 : 0); } protected CompetitorColumn createCompetitorColumn() { @@ -2949,7 +2981,8 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS : preSelectedRace.getRaceName(), /* don't change nameOfRaceColumnToShow */null, /* set nameOfRaceToShow if race was pre-selected */preSelectedRace == null ? null : preSelectedRace - .getRaceName(), getRaceColumnSelection(), /* leave showRegattaRank and overall details unchanged */ null)); + .getRaceName(), getRaceColumnSelection(), /* leave showRegattaRank and overall details unchanged */ null, + /* take into account state of competitor columns*/isShowCompetitorSailId(), isShowCompetitorFullName())); } currentlyHandlingPlayStateChange = false; oldPlayMode = playMode; @@ -2988,7 +3021,8 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS Collections.unmodifiableList(selectedLegDetails), Collections.unmodifiableList(selectedRaceDetails), Collections.unmodifiableList(selectedOverallDetailColumns), /* All races to select */ leaderboard.getRaceList(), raceColumnSelection.getSelectedRaceColumnsOrderedAsInLeaderboard(leaderboard), raceColumnSelection, autoExpandPreSelectedRace, - isShowAddedScores(), timer.getRefreshInterval(), isShowOverallColumnWithNumberOfRacesCompletedPerCompetitor(), stringMessages); + isShowAddedScores(), timer.getRefreshInterval(), isShowOverallColumnWithNumberOfRacesCompletedPerCompetitor(), + isShowCompetitorSailId(), isShowCompetitorFullName(), stringMessages); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettings.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettings.java index d97d4fb8152..a1e52983096 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettings.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettings.java @@ -85,6 +85,9 @@ public class LeaderboardSettings { */ private final boolean showAddedScores; + private final boolean showCompetitorSailIdColumn; + private final boolean showCompetitorFullNameColumn; + /** * Show a column with total number of races completed */ @@ -98,7 +101,8 @@ public class LeaderboardSettings { List namesOfRaceColumnsToShow, List namesOfRacesToShow, Integer numberOfLastRacesToShow, boolean autoExpandPreSelectedRace, Long delayBetweenAutoAdvancesInMilliseconds, String nameOfRaceToSort, boolean sortAscending, boolean updateUponPlayStateChange, RaceColumnSelectionStrategies activeRaceColumnSelectionStrategy, - boolean showAddedScores, boolean showOverallColumnWithNumberOfRacesCompletedPerCompetitor) { + boolean showAddedScores, boolean showOverallColumnWithNumberOfRacesCompletedPerCompetitor, + boolean showCompetitorSailIdColumn, boolean showCompetitorFullNameColumn) { if (namesOfRacesToShow != null && namesOfRaceColumnsToShow != null) { throw new IllegalArgumentException("You can identify races either only by their race or by their column names, not both"); } @@ -116,6 +120,8 @@ public class LeaderboardSettings { this.sortAscending = sortAscending; this.updateUponPlayStateChange = updateUponPlayStateChange; this.showAddedScores = showAddedScores; + this.showCompetitorSailIdColumn = showCompetitorSailIdColumn; + this.showCompetitorFullNameColumn = showCompetitorFullNameColumn; this.showOverallColumnWithNumberOfRacesCompletedPerCompetitor = showOverallColumnWithNumberOfRacesCompletedPerCompetitor; } @@ -198,4 +204,12 @@ public class LeaderboardSettings { public boolean isShowOverallColumnWithNumberOfRacesCompletedPerCompetitor() { return showOverallColumnWithNumberOfRacesCompletedPerCompetitor; } + + public boolean isShowCompetitorSailIdColumn() { + return showCompetitorSailIdColumn; + } + + public boolean isShowCompetitorFullNameColumn() { + return showCompetitorFullNameColumn; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsDialogComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsDialogComponent.java index 6e7a4414627..c49feffacdd 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsDialogComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsDialogComponent.java @@ -48,6 +48,8 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone private final boolean autoExpandPreSelectedRace; private final boolean showAddedScores; private final boolean showOverallColumnWithNumberOfRacesSailedPerCompetitor; + private final boolean showCompetitorSailIdColumn; + private final boolean showCompetitorFullNameColumn; private final long delayBetweenAutoAdvancesInMilliseconds; private final Integer numberOfLastRacesToShow; private RaceColumnSelectionStrategies activeRaceColumnSelectionStrategy; @@ -56,6 +58,8 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone private IntegerBox numberOfLastRacesToShowBox; private CheckBox showAddedScoresCheckBox; private CheckBox showOverallColumnWithNumberOfRacesSailedPerCompetitorCheckBox; + private CheckBox showCompetitorSailIdColumnheckBox; + private CheckBox showCompetitorFullNameColumnCheckBox; protected LeaderboardSettingsDialogComponent(List maneuverDetailSelection, List legDetailSelection, List raceDetailSelection, @@ -63,6 +67,7 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone Iterable raceColumnSelection, RaceColumnSelection raceColumnSelectionStrategy, boolean autoExpandPreSelectedRace, boolean showAddedScores, long delayBetweenAutoAdvancesInMilliseconds, boolean showOverallColumnWithNumberOfRacesSailedPerCompetitor, + boolean showCompetitorSailIdColumn, boolean showCompetitorFullNameColumn, StringMessages stringMessages) { this.raceAllRaceColumns = raceAllRaceColumns; this.numberOfLastRacesToShow = raceColumnSelectionStrategy.getNumberOfLastRaceColumnsToShow(); @@ -82,6 +87,8 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone this.delayBetweenAutoAdvancesInMilliseconds = delayBetweenAutoAdvancesInMilliseconds; this.showAddedScores = showAddedScores; this.showOverallColumnWithNumberOfRacesSailedPerCompetitor = showOverallColumnWithNumberOfRacesSailedPerCompetitor; + this.showCompetitorSailIdColumn = showCompetitorSailIdColumn; + this.showCompetitorFullNameColumn = showCompetitorFullNameColumn; } @Override @@ -184,6 +191,12 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone dialog.addTooltip(showOverallColumnWithNumberOfRacesSailedPerCompetitorCheckBox, stringMessages.showNumberOfRacesScored()); showOverallColumnWithNumberOfRacesSailedPerCompetitorCheckBox.setValue(showOverallColumnWithNumberOfRacesSailedPerCompetitor); overallDetailDialogContent.add(showOverallColumnWithNumberOfRacesSailedPerCompetitorCheckBox); + showCompetitorSailIdColumnheckBox = dialog.createCheckbox(stringMessages.showCompetitorSailIdColumn()); + showCompetitorSailIdColumnheckBox.setValue(showCompetitorSailIdColumn); + overallDetailDialogContent.add(showCompetitorSailIdColumnheckBox); + showCompetitorFullNameColumnCheckBox = dialog.createCheckbox(stringMessages.showCompetitorFullNameColumn()); + showCompetitorFullNameColumnCheckBox.setValue(showCompetitorFullNameColumn); + overallDetailDialogContent.add(showCompetitorFullNameColumnCheckBox); overallDetailDialog.add(overallDetailDialogContent); return overallDetailDialog; } @@ -371,7 +384,8 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone null, true, /* updateUponPlayStateChange */ true, activeRaceColumnSelectionStrategy, /*showAddedScores*/ showAddedScoresCheckBox.getValue().booleanValue(), - /*showOverallColumnWithNumberOfRacesSailedPerCompetitor*/ showOverallColumnWithNumberOfRacesSailedPerCompetitorCheckBox.getValue().booleanValue()); + /*showOverallColumnWithNumberOfRacesSailedPerCompetitor*/ showOverallColumnWithNumberOfRacesSailedPerCompetitorCheckBox.getValue().booleanValue(), + showCompetitorSailIdColumnheckBox.getValue(), showCompetitorFullNameColumnCheckBox.getValue()); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsFactory.java index fff017843fd..f4036493976 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsFactory.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsFactory.java @@ -45,7 +45,8 @@ public class LeaderboardSettingsFactory { * overall details if and only if this parameter is true */ public LeaderboardSettings createNewSettingsForPlayMode(PlayModes playMode, String nameOfRaceToSort, String nameOfRaceColumnToShow, - String nameOfRaceToShow, RaceColumnSelection raceColumnSelection, Boolean showRegattaRank) { + String nameOfRaceToShow, RaceColumnSelection raceColumnSelection, Boolean showRegattaRank, boolean showCompetitorSailIdColumn, + boolean showCompetitorNameColumn) { if (nameOfRaceColumnToShow != null && nameOfRaceToShow != null) { throw new IllegalArgumentException("Can identify only one race to show, either by race name or by its column name, but not both"); } @@ -76,7 +77,8 @@ public class LeaderboardSettingsFactory { /* set autoExpandPreSelectedRace to true if we look at a single race */ nameOfRaceColumnToShow != null || nameOfRaceToShow != null, /* refresh interval */ null, /* name of race to sort */ nameOfRaceToSort, /* ascending */ true, /* updateUponPlayStateChange */ true, raceColumnSelection.getType(), - /*showAddedScores*/ false, /*showOverallRacesCompleted*/ false); + /*showAddedScores*/ false, /*showOverallRacesCompleted*/ false, + /*showCompetitorSailIdColumns*/showCompetitorSailIdColumn, /*showCompetitorFullNameColumn*/showCompetitorNameColumn); break; case Replay: settings = createNewDefaultSettings(namesOfRaceColumnsToShow, namesOfRacesToShow, nameOfRaceToSort, /* autoExpandFirstRace */ @@ -165,7 +167,8 @@ public class LeaderboardSettingsFactory { namesOfRacesToShow, numberOfLastRacesToShow, autoExpandPreSelectedRace, refreshIntervalMillis, /* sort by column */ nameOfRaceToSort, /* ascending */ true, /* updateUponPlayStateChange */ true, raceColumnSelectionStrategy, - /*showAddedScores*/ false, /*showOverallRacesCompleted*/ false); + /*showAddedScores*/ false, /*showOverallRacesCompleted*/ false, + /*showCompetitorSailIdColumns*/true, /*showCompetitorFullNameColumn*/true); } public LeaderboardSettings mergeLeaderboardSettings(LeaderboardSettings settingsWithRaceSelection, LeaderboardSettings settingsWithDetails) { @@ -180,6 +183,8 @@ public class LeaderboardSettingsFactory { List namesOfRacesToShow = copyRaceNamesList(settingsWithRaceSelection.getNamesOfRacesToShow()); Integer numberOfLastRacesToShow = settingsWithRaceSelection.getNumberOfLastRacesToShow(); boolean autoExpandPreSelectedRace = settingsWithRaceSelection.isAutoExpandPreSelectedRace(); + boolean showCompetitorSailIdColumn = settingsWithRaceSelection.isShowCompetitorSailIdColumn(); + boolean showCompetitorFullNameColumns = settingsWithRaceSelection.isShowCompetitorFullNameColumn(); String nameOfRaceToSort = settingsWithRaceSelection.getNameOfRaceToSort(); boolean sortAscending = settingsWithRaceSelection.isSortAscending(); boolean updateUponPlayStateChange = settingsWithRaceSelection.isUpdateUponPlayStateChange(); @@ -187,7 +192,7 @@ public class LeaderboardSettingsFactory { return new LeaderboardSettings(maneuverDetails, legDetails, raceDetails, overallDetailsToShow, namesOfRaceColumnsToShow, namesOfRacesToShow, numberOfLastRacesToShow, autoExpandPreSelectedRace, refreshIntervalInMs, nameOfRaceToSort, sortAscending, updateUponPlayStateChange, strategy, /*showAddedScores*/ false, - /*showOverallRacesCompleted*/ false); + /*showOverallRacesCompleted*/ false, showCompetitorSailIdColumn, showCompetitorFullNameColumns); } private List copyDetailTypes(List detailTypes) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardUrlConfigurationDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardUrlConfigurationDialog.java index 0f0a6734a3b..24d783947f6 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardUrlConfigurationDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardUrlConfigurationDialog.java @@ -112,7 +112,8 @@ public class LeaderboardUrlConfigurationDialog extends SettingsDialog(); + referencesToGarbageCollectedRaces = new ReferenceQueue<>(); new Thread("QuickRanksLiveCache garbage collector") { @Override public void run() { @@ -107,7 +108,6 @@ public class QuickRanksLiveCache { logger.info("Received stop in QuickRanksLiveCache garbage collector; terminating"); } }.start(); - referencesToGarbageCollectedRaces = new ReferenceQueue<>(); } private void remove(RegattaAndRaceIdentifier raceIdentifier) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 3fc62904e07..c1aaec2a233 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -418,6 +418,7 @@ import com.sap.sse.common.Util.Triple; import com.sap.sse.common.impl.MillisecondsDurationImpl; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.common.impl.TimeRangeImpl; +import com.sap.sse.common.mail.MailException; import com.sap.sse.common.search.KeywordQuery; import com.sap.sse.common.search.Result; import com.sap.sse.filestorage.FileStorageService; @@ -431,8 +432,10 @@ import com.sap.sse.replication.ReplicationFactory; import com.sap.sse.replication.ReplicationMasterDescriptor; import com.sap.sse.replication.ReplicationService; import com.sap.sse.replication.impl.ReplicaDescriptor; +import com.sap.sse.util.ServiceTrackerFactory; import com.sapsailing.xrr.structureimport.eventimport.RegattaJSON; + /** * The server side implementation of the RPC service. */ @@ -496,6 +499,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S private final SwissTimingReplayService swissTimingReplayService; private final QuickRanksLiveCache quickRanksLiveCache; + public SailingServiceImpl() { BundleContext context = Activator.getDefault(); Activator activator = Activator.getInstance(); @@ -503,21 +507,25 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S activator.setSailingService(this); // register so this service is informed when the bundle shuts down } quickRanksLiveCache = new QuickRanksLiveCache(this); - racingEventServiceTracker = createAndOpenRacingEventServiceTracker(context); - replicationServiceTracker = createAndOpenReplicationServiceTracker(context); - resultUrlRegistryServiceTracker = createAndOpenResultUrlRegistryServiceTracker(context); - swissTimingAdapterTracker = createAndOpenSwissTimingAdapterTracker(context); - tractracAdapterTracker = createAndOpenTracTracAdapterTracker(context); - raceLogTrackingAdapterTracker = createAndOpenRaceLogTrackingAdapterTracker(context); - deviceIdentifierStringSerializationHandlerTracker = createAndOpenDeviceIdentifierStringSerializationHandlerTracker(context); - igtimiAdapterTracker = createAndOpenIgtimiTracker(context); + racingEventServiceTracker = ServiceTrackerFactory.createAndOpen(context, RacingEventService.class); + replicationServiceTracker = ServiceTrackerFactory.createAndOpen(context, ReplicationService.class); + resultUrlRegistryServiceTracker = ServiceTrackerFactory.createAndOpen(context, ResultUrlRegistry.class); + swissTimingAdapterTracker = ServiceTrackerFactory.createAndOpen(context, SwissTimingAdapterFactory.class); + tractracAdapterTracker = ServiceTrackerFactory.createAndOpen(context, TracTracAdapterFactory.class); + raceLogTrackingAdapterTracker = ServiceTrackerFactory.createAndOpen(context, + RaceLogTrackingAdapterFactory.class); + deviceIdentifierStringSerializationHandlerTracker = ServiceTrackerFactory.createAndOpen(context, + DeviceIdentifierStringSerializationHandler.class); + igtimiAdapterTracker = ServiceTrackerFactory.createAndOpen(context, IgtimiConnectionFactory.class); baseDomainFactory = getService().getBaseDomainFactory(); mongoObjectFactory = getService().getMongoObjectFactory(); domainObjectFactory = getService().getDomainObjectFactory(); // TODO what about passing on the mongo/domain object factory to obtain an according SwissTimingAdapterPersistence instance similar to how the tractracDomainObjectFactory etc. are created below? swissTimingAdapterPersistence = SwissTimingAdapterPersistence.INSTANCE; - swissTimingReplayService = getSwissTimingReplayService(context); - scoreCorrectionProviderServiceTracker = createAndOpenScoreCorrectionProviderServiceTracker(context); + swissTimingReplayService = ServiceTrackerFactory.createAndOpen(context, SwissTimingReplayServiceFactory.class) + .getService().createSwissTimingReplayService(getSwissTimingAdapter().getSwissTimingDomainFactory()); + scoreCorrectionProviderServiceTracker = ServiceTrackerFactory.createAndOpen(context, + ScoreCorrectionProvider.class); tractracDomainObjectFactory = com.sap.sailing.domain.tractracadapter.persistence.PersistenceFactory.INSTANCE .createDomainObjectFactory(mongoObjectFactory.getDatabase(), getTracTracAdapter() .getTracTracDomainFactory()); @@ -556,89 +564,26 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S quickRanksLiveCache.stop(); } - protected SwissTimingReplayService getSwissTimingReplayService(BundleContext context) { - return createAndOpenSwissTimingReplayServiceTracker(context).getService().createSwissTimingReplayService(getSwissTimingAdapter().getSwissTimingDomainFactory()); + protected SwissTimingAdapterFactory getSwissTimingAdapterFactory() { + return swissTimingAdapterTracker.getService(); } protected SwissTimingAdapter getSwissTimingAdapter() { - return swissTimingAdapterTracker.getService().getOrCreateSwissTimingAdapter(baseDomainFactory); + return getSwissTimingAdapterFactory().getOrCreateSwissTimingAdapter(baseDomainFactory); + } + + protected TracTracAdapterFactory getTracTracAdapterFactory() { + return tractracAdapterTracker.getService(); } protected TracTracAdapter getTracTracAdapter() { - return tractracAdapterTracker.getService().getOrCreateTracTracAdapter(baseDomainFactory); - } - - protected ServiceTracker createAndOpenTracTracAdapterTracker(BundleContext context) { - ServiceTracker result = new ServiceTracker( - context, TracTracAdapterFactory.class.getName(), null); - result.open(); - return result; - } - - protected ServiceTracker createAndOpenIgtimiTracker(BundleContext context) { - ServiceTracker result = new ServiceTracker( - context, IgtimiConnectionFactory.class.getName(), null); - result.open(); - return result; - } - - protected ServiceTracker createAndOpenSwissTimingAdapterTracker( - BundleContext context) { - ServiceTracker result = new ServiceTracker( - context, SwissTimingAdapterFactory.class.getName(), null); - result.open(); - return result; - } - - protected ServiceTracker createAndOpenSwissTimingReplayServiceTracker( - BundleContext context) { - ServiceTracker result = new ServiceTracker( - context, SwissTimingReplayServiceFactory.class.getName(), null); - result.open(); - return result; - } - - protected ServiceTracker createAndOpenRaceLogTrackingAdapterTracker( - BundleContext context) { - ServiceTracker result = - new ServiceTracker( - context, RaceLogTrackingAdapterFactory.class.getName(), null); - result.open(); - return result; + return getTracTracAdapterFactory().getOrCreateTracTracAdapter(baseDomainFactory); } private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); } - protected ServiceTracker createAndOpenRacingEventServiceTracker( - BundleContext context) { - ServiceTracker result = new ServiceTracker( - context, RacingEventService.class.getName(), null); - result.open(); - return result; - } - - protected ServiceTracker createAndOpenResultUrlRegistryServiceTracker( - BundleContext context) { - ServiceTracker result = new ServiceTracker( - context, ResultUrlRegistry.class.getName(), null); - result.open(); - return result; - } - - /** - * Asks the OSGi system for registered score correction provider services - */ - protected ServiceTracker createAndOpenScoreCorrectionProviderServiceTracker( - BundleContext bundleContext) { - ServiceTracker tracker = new ServiceTracker(bundleContext, - ScoreCorrectionProvider.class.getName(), - /* customizer */null); - tracker.open(); - return tracker; - } - @Override public Iterable getScoreCorrectionProviderNames() { List result = new ArrayList(); @@ -685,15 +630,6 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return new ScoreCorrectionProviderDTO(scoreCorrectionProvider.getName(), hasResultsForBoatClassFromDateByEventName); } - protected ServiceTracker createAndOpenReplicationServiceTracker( - BundleContext context) { - ServiceTracker result = - new ServiceTracker( - context, ReplicationService.class.getName(), null); - result.open(); - return result; - } - /** * If date is null, the {@link LiveLeaderboardUpdater} for the * leaderboardName requested is obtained or created if it doesn't exist yet. The request is then passed @@ -832,7 +768,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S regattaDTO.races = convertToRaceDTOs(regatta); regattaDTO.series = convertToSeriesDTOs(regatta); regattaDTO.startDate = regatta.getStartDate() != null ? regatta.getStartDate().asDate() : null; - regattaDTO.endDate = regatta.getStartDate() != null ? regatta.getEndDate().asDate() : null; + regattaDTO.endDate = regatta.getEndDate() != null ? regatta.getEndDate().asDate() : null; BoatClass boatClass = regatta.getBoatClass(); if (boatClass != null) { regattaDTO.boatClass = new BoatClassDTO(boatClass.getName(), boatClass.getDisplayName(), boatClass.getHullLength().getMeters()); @@ -1116,10 +1052,6 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } } - private SwissTimingReplayService getSwissTimingReplayService() { - return swissTimingReplayService; - } - @Override public List getPreviousTracTracConfigurations() throws Exception { Iterable configs = tractracDomainObjectFactory.getTracTracConfigurations(); @@ -1473,7 +1405,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier); WindInfoForRaceDTO result = null; if (trackedRace != null) { - TimePoint fromTimePoint = from == null ? trackedRace.getStartOfTracking() : new MillisecondsTimePoint(from); + TimePoint fromTimePoint = from == null ? trackedRace.getStartOfTracking() == null ? trackedRace + .getStartOfRace() : trackedRace.getStartOfTracking() : new MillisecondsTimePoint(from); TimePoint toTimePoint = to == null ? trackedRace.getEndOfRace() == null ? MillisecondsTimePoint.now().minus(trackedRace.getDelayToLiveInMillis()) : trackedRace.getEndOfRace() : new MillisecondsTimePoint(to); if (fromTimePoint != null && toTimePoint != null) { @@ -2239,7 +2172,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return racingEventServiceTracker.getService(); // grab the service } - private ReplicationService getReplicationService() { + protected ReplicationService getReplicationService() { return replicationServiceTracker.getService(); } @@ -2699,6 +2632,10 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } } } + + protected SwissTimingReplayService getSwissTimingReplayService() { + return swissTimingReplayService; + } @Override public List listSwissTiminigReplayRaces(String swissTimingUrl) { @@ -4525,7 +4462,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public CompetitorDTO addOrUpdateCompetitor(CompetitorDTO competitor) { + public CompetitorDTO addOrUpdateCompetitor(CompetitorDTO competitor) throws URISyntaxException { Competitor existingCompetitor = getService().getCompetitorStore().getExistingCompetitorByIdAsString(competitor.getIdAsString()); Nationality nationality = (competitor.getThreeLetterIocCountryCode() == null || competitor.getThreeLetterIocCountryCode().isEmpty()) ? null : getBaseDomainFactory().getOrCreateNationality(competitor.getThreeLetterIocCountryCode()); @@ -4538,12 +4475,13 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S DynamicBoat boat = new BoatImpl(competitor.getName() + " boat", boatClass, competitor.getSailID()); result = getBaseDomainFactory().convertToCompetitorDTO( getBaseDomainFactory().getOrCreateCompetitor(UUID.randomUUID(), competitor.getName(), - competitor.getColor(), team, boat)); + competitor.getColor(), competitor.getEmail(), team, boat)); } else { result = getBaseDomainFactory().convertToCompetitorDTO( getService().apply( new UpdateCompetitor(competitor.getIdAsString(), competitor.getName(), competitor - .getColor(), competitor.getSailID(), nationality, existingCompetitor.getTeam().getImage()))); + .getColor(), competitor.getEmail(), competitor.getSailID(), nationality, + competitor.getImageURL()==null?null:new URI(competitor.getImageURL())))); } return result; } @@ -4736,9 +4674,13 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S private IgtimiConnectionFactory getIgtimiConnectionFactory() { return igtimiAdapterTracker.getService(); } + + protected RaceLogTrackingAdapterFactory getRaceLogTrackingAdapterFactory() { + return raceLogTrackingAdapterTracker.getService(); + } protected RaceLogTrackingAdapter getRaceLogTrackingAdapter() { - return raceLogTrackingAdapterTracker.getService().getAdapter(getBaseDomainFactory()); + return getRaceLogTrackingAdapterFactory().getAdapter(getBaseDomainFactory()); } @Override @@ -5180,16 +5122,6 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S raceLog.add(event); } - private ServiceTracker - createAndOpenDeviceIdentifierStringSerializationHandlerTracker(BundleContext context) { - ServiceTracker tracker = - new ServiceTracker( - context, DeviceIdentifierStringSerializationHandler.class, null); - - tracker.open(); - return tracker; - } - @Override public List getDeserializableDeviceIdentifierTypes() { List result = new ArrayList(); @@ -5359,73 +5291,58 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S @Override public PolarSheetsXYDiagramData createXYDiagramForBoatClass(String boatClassName) { BoatClass boatClass = getService().getBaseDomainFactory().getOrCreateBoatClass(boatClassName); - List> pointsForUpwindStarboardAverageSpeedMovingAverage = new ArrayList>(); - List> pointsForUpwindStarboardAverageConfidence = new ArrayList>(); - List> pointsForUpwindPortAverageSpeedMovingAverage = new ArrayList>(); - List> pointsForUpwindPortAverageConfidence = new ArrayList>(); - List> pointsForDownwindStarboardAverageSpeedMovingAverage = new ArrayList>(); - List> pointsForDownwindStarboardAverageConfidence = new ArrayList>(); - List> pointsForDownwindPortAverageSpeedMovingAverage = new ArrayList>(); - List> pointsForDownwindPortAverageConfidence = new ArrayList>(); - for (double windInKnots = 0.1; windInKnots < 30; windInKnots = windInKnots + 0.1) { - try { - SpeedWithBearingWithConfidence averageUpwindStarboardMovingAverage = getService() - .getPolarDataService().getAverageSpeedWithBearing(boatClass, new KnotSpeedImpl(windInKnots), - LegType.UPWIND, Tack.STARBOARD); - pointsForUpwindStarboardAverageSpeedMovingAverage.add(new Pair(windInKnots, - averageUpwindStarboardMovingAverage.getObject().getKnots())); - - pointsForUpwindStarboardAverageConfidence.add(new Pair(windInKnots, - averageUpwindStarboardMovingAverage.getConfidence())); - } catch (NotEnoughDataHasBeenAddedException e) { - // Do not add a point to the result + Map, List>> movingAverageSpeedDataLists = new HashMap<>(); + Map, List>> regressionSpeedDataLists = new HashMap<>(); + Map, List>> averageConfidenceDataLists = new HashMap<>(); + for (LegType legType : new LegType[] { LegType.UPWIND, LegType.DOWNWIND }) { + for (Tack tack : new Tack[] { Tack.PORT, Tack.STARBOARD }) { + movingAverageSpeedDataLists.put(new Pair(legType, tack), + new ArrayList>()); + regressionSpeedDataLists.put(new Pair(legType, tack), + new ArrayList>()); + averageConfidenceDataLists.put(new Pair(legType, tack), + new ArrayList>()); } - - try { - SpeedWithBearingWithConfidence averageUpwindPortMovingAverage = getService() - .getPolarDataService().getAverageSpeedWithBearing(boatClass, new KnotSpeedImpl(windInKnots), - LegType.UPWIND, Tack.PORT); - pointsForUpwindPortAverageSpeedMovingAverage.add(new Pair(windInKnots, - averageUpwindPortMovingAverage.getObject().getKnots())); - - pointsForUpwindPortAverageConfidence.add(new Pair(windInKnots, - averageUpwindPortMovingAverage.getConfidence())); - } catch (NotEnoughDataHasBeenAddedException e) { - // Do not add a point to the result - } - - try { - SpeedWithBearingWithConfidence averageDownwindStarboardMovingAverage = getService() - .getPolarDataService().getAverageSpeedWithBearing(boatClass, new KnotSpeedImpl(windInKnots), - LegType.DOWNWIND, Tack.STARBOARD); - pointsForDownwindStarboardAverageSpeedMovingAverage.add(new Pair(windInKnots, - averageDownwindStarboardMovingAverage.getObject().getKnots())); - - pointsForDownwindStarboardAverageConfidence.add(new Pair(windInKnots, - averageDownwindStarboardMovingAverage.getConfidence())); - } catch (NotEnoughDataHasBeenAddedException e) { - // Do not add a point to the result - } - - try { - SpeedWithBearingWithConfidence averageDownwindPortMovingAverage = getService() - .getPolarDataService().getAverageSpeedWithBearing(boatClass, new KnotSpeedImpl(windInKnots), - LegType.DOWNWIND, Tack.PORT); - pointsForDownwindPortAverageSpeedMovingAverage.add(new Pair(windInKnots, - averageDownwindPortMovingAverage.getObject().getKnots())); - - pointsForDownwindPortAverageConfidence.add(new Pair(windInKnots, - averageDownwindPortMovingAverage.getConfidence())); - } catch (NotEnoughDataHasBeenAddedException e) { - // Do not add a point to the result - } - } - PolarSheetsXYDiagramData data = new PolarSheetsXYDiagramDataImpl( - pointsForUpwindStarboardAverageSpeedMovingAverage, pointsForUpwindStarboardAverageConfidence, - pointsForUpwindPortAverageSpeedMovingAverage, pointsForUpwindPortAverageConfidence, - pointsForDownwindStarboardAverageSpeedMovingAverage, pointsForDownwindStarboardAverageConfidence, - pointsForDownwindPortAverageSpeedMovingAverage, pointsForDownwindPortAverageConfidence); + for (double windInKnots = 0.1; windInKnots < 30; windInKnots = windInKnots + 0.1) { + for (LegType legType : new LegType[] { LegType.UPWIND, LegType.DOWNWIND }) { + for (Tack tack : new Tack[] { Tack.PORT, Tack.STARBOARD }) { + + try { + SpeedWithBearingWithConfidence averageUpwindStarboardMovingAverage = getService() + .getPolarDataService().getAverageSpeedWithBearing(boatClass, + new KnotSpeedImpl(windInKnots), legType, tack, false); + + movingAverageSpeedDataLists.get(new Pair(legType, tack)).add( + new Pair(windInKnots, averageUpwindStarboardMovingAverage.getObject() + .getKnots())); + + + averageConfidenceDataLists.get(new Pair(legType, tack)).add( + new Pair(windInKnots, averageUpwindStarboardMovingAverage + .getConfidence())); + + } catch (NotEnoughDataHasBeenAddedException e) { + // Do not add a point to the result + } + + try { + SpeedWithBearingWithConfidence averageUpwindStarboardRegression = getService() + .getPolarDataService().getAverageSpeedWithBearing(boatClass, + new KnotSpeedImpl(windInKnots), legType, tack, true); + + regressionSpeedDataLists.get(new Pair(legType, tack)).add( + new Pair(windInKnots, averageUpwindStarboardRegression.getObject() + .getKnots())); + } catch (NotEnoughDataHasBeenAddedException e) { + // Do not add a point to the result + } + } + } + } + + PolarSheetsXYDiagramData data = new PolarSheetsXYDiagramDataImpl(movingAverageSpeedDataLists, + regressionSpeedDataLists, averageConfidenceDataLists); return data; } @@ -5436,13 +5353,16 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } return getService().getFileStorageManagementService().getFileStorageService(name); } + + private Locale getLocale(String localeInfoName) { + return ResourceBundleStringMessages.Util.getLocaleFor(localeInfoName); + } @Override public FileStorageServiceDTO[] getAvailableFileStorageServices(String localeInfoName) { - Locale locale = ResourceBundleStringMessages.Util.getLocaleFor(localeInfoName); List serviceDtos = new ArrayList<>(); for (FileStorageService s : getService().getFileStorageManagementService().getAvailableFileStorageServices()) { - serviceDtos.add(FileStorageServiceDTOUtils.convert(s, locale)); + serviceDtos.add(FileStorageServiceDTOUtils.convert(s, getLocale(localeInfoName))); } return serviceDtos.toArray(new FileStorageServiceDTO[0]); } @@ -5460,15 +5380,14 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public FileStorageServicePropertyErrorsDTO testFileStorageServiceProperties(String serviceName, String localeInfoName) { - Locale locale = ResourceBundleStringMessages.Util.getLocaleFor(localeInfoName); + public FileStorageServicePropertyErrorsDTO testFileStorageServiceProperties(String serviceName, String localeInfoName) throws IOException { try { FileStorageService service = getFileStorageService(serviceName); if (service != null) { service.testProperties(); } } catch (InvalidPropertiesException e) { - return FileStorageServiceDTOUtils.convert(e, locale); + return FileStorageServiceDTOUtils.convert(e, getLocale(localeInfoName)); } return null; } @@ -5486,4 +5405,17 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return null; } } + + @Override + public void inviteCompetitorsForTrackingViaEmail(String serverUrlWithoutTrailingSlash, EventDTO eventDto, + String leaderboardName, Set competitorDtos, String localeInfoName) throws MailException { + Event event = getService().getEvent(eventDto.id); + Set competitors = new HashSet<>(); + for (CompetitorDTO c : competitorDtos) { + competitors.add(getCompetitor(c)); + } + Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName); + getRaceLogTrackingAdapter().inviteCompetitorsForTrackingViaEmail(event, leaderboard, serverUrlWithoutTrailingSlash, + competitors, getLocale(localeInfoName)); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java index b077beda8e0..fe012ee543c 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java @@ -39,7 +39,7 @@ import com.sap.sse.gwt.client.player.Timer; * */ public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay { - public static final String LODA_WIND_STREAMLET_DATA_CATEGORY = "loadWindStreamletData"; + public static final String LOAD_WIND_STREAMLET_DATA_CATEGORY = "loadWindStreamletData"; private static final int animationIntervalMillis = 40; private static final long RESOLUTION_IN_MILLIS = 5000; private static final int WIND_FETCH_INTERVAL_IN_MILLIS = 10000; @@ -176,7 +176,7 @@ public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay { } GetWindInfoAction getWind = new GetWindInfoAction(sailingService, raceIdentifier, timeOfLastFixOfSource, /* endOfTime */ null, RESOLUTION_IN_MILLIS, windSourceTypeNames, /* onlyUpToNewestEvent */ true); - asyncActionsExecutor.execute(getWind, LODA_WIND_STREAMLET_DATA_CATEGORY, + asyncActionsExecutor.execute(getWind, LOAD_WIND_STREAMLET_DATA_CATEGORY, new MarkedAsyncCallback<>(new AsyncCallback() { @Override public void onFailure(Throwable caught) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/SailingAnalyticsNotes.html b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/SailingAnalyticsNotes.html index b8fe7d2e47c..fba94343b2e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/SailingAnalyticsNotes.html +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/SailingAnalyticsNotes.html @@ -180,7 +180,7 @@
  • China races, such as in Qingdao, may have the map background displayed at slightly wrong locations. This is due to an intentional random transformation that Google applies to GPS locations for China. You can circumvent this issue by switching to satellite view where the map background is displayed - correctly. + correctly.
  • Fixed a bug that in some cases has caused the wind chart, the advantage line, the wind indicator on the map and the leaderboard to use slightly different wind values. The problem was related to different averaging and diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini b/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini index 2958f1e5360..8997a7fd30a 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini @@ -1,50 +1,55 @@ [main] -shiro.loginUrl = /security/ui/Login.html -shiro.successUrl = /UserManagement.html -# Use Built-in Chache Manager - -anyofroles = com.sap.sse.security.AnyOfRolesFilter -anyofroles.loginUrl = ../security/ui/Login.html - credentialsMatcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher -# base64 encoding, not hex in this example: credentialsMatcher.storedCredentialsHexEncoded = false credentialsMatcher.hashIterations = 1024 +# Realm configuration: +permissionsForRoleProvider = com.sap.sailing.domain.common.security.SailingPermissionsForRoleProvider upRealm = com.sap.sse.security.UsernamePasswordRealm upRealm.credentialsMatcher = $credentialsMatcher - +upRealm.permissionsForRoleProvider = $permissionsForRoleProvider oauthRealm = com.sap.sse.security.OAuthRealm +oauthRealm.permissionsForRoleProvider = $permissionsForRoleProvider +bearerTokenRealm = com.sap.sse.security.BearerTokenRealm +bearerTokenRealm.permissionsForRoleProvider = $permissionsForRoleProvider -securityManager.realms = $upRealm, $oauthRealm - +# Putting it all together: sessionManager = com.sap.sse.security.SecurityWebSessionManager securityManager.sessionManager = $sessionManager -#globalCookie = com.sap.sse.security.GlobalCookie -#securityManager.sessionManager.sessionIdCookie = $globalCookie - -authc = com.sap.sse.security.CustomFilter -authc.loginUrl = ../security/ui/Login.html?app=Sailing%20Analytics -authc.successUrl = /security/ui/UserManagement.html - -roles.loginUrl = ../security/ui/Login.html - sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO securityManager.sessionManager.sessionDAO = $sessionDAO - cacheManager = com.sap.sse.security.SessionCacheManager securityManager.cacheManager = $cacheManager -#securityManager.sessionMode = native +# Authentication Filter Configurations +# ------------------------------------ + +# Set default login and success URLs for all filters: +shiro.loginUrl = ../security/ui/Login.html?app=Sailing%20Analytics +shiro.successUrl = ../security/ui/UserManagement.html + +# A filter that requires the user to have at least one of the roles specified as +# filter parameter. This makes it different from the default roles[...] filter +# which requires the user to have ALL of the roles specified as parameters. +anyofroles = com.sap.sse.security.AnyOfRolesFilter + +# A custom filter for GWT pages that require an authenticated user. +# Forwards unauthenticated users to the default login page. When +# addressed directly (and not based on a redirect), users who logged +# on successfully will be redirected to the UserManagement.html page +# where they can adjust their profile. +customGwt = com.sap.sse.security.CustomFilter + +# This authentication filter accepts a bearer access token in the HTTP +# Authorization header field, as in +# Authorization: Bearer 1029741026501365024376093245 +# If no such bearer token is provided, the filter falls back to basic HTTP +# authentication ("Authentication: Basic ...") and then regular form-based +# authentication with POST parameters "username" and "password". +bearerToken = com.sap.sse.security.BearerTokenOrBasicOrFormAuthenticationFilter [urls] -# The /security/ui/Login.html is not restricted to authenticated users (otherwise no one could log in!), but -# the 'authc' filter must still be specified for it so it can process that url's -# login submissions. It is 'smart' enough to allow those requests through as specified by the -# shiro.loginUrl above. - -/security/ui/UserManagement.html = authc,roles[admin] -/LeaderboardEditing.html = authc,anyofroles[admin,eventmanager] +/LeaderboardEditing.html = customGwt,anyofroles[admin,eventmanager] # The admin console displays its features based on the user's roles -/AdminConsole.html = authc +/AdminConsole.html = customGwt diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreAndLoadDeviceIdentifierTest.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreAndLoadDeviceIdentifierTest.java index 08ece8cbcc0..dad04664a00 100644 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreAndLoadDeviceIdentifierTest.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreAndLoadDeviceIdentifierTest.java @@ -82,7 +82,7 @@ public class StoreAndLoadDeviceIdentifierTest extends AbstractMongoDBTest { private DeviceIdentifier storeAndLoad(DeviceIdentifier device, TypeBasedServiceFinderFactory forStoring, TypeBasedServiceFinderFactory forLoading) { createFactories(forStoring); - Competitor c = new CompetitorImpl("a", "a", null, null, null); + Competitor c = new CompetitorImpl("a", "a", null, null, null, null); raceLog.add(RaceLogEventFactory.INSTANCE.createDeviceCompetitorMappingEvent(now(), author, device, c, 0, now(), now())); diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreAndLoadRaceLogEventsTest.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreAndLoadRaceLogEventsTest.java index 84b39d90664..d40fac7e98b 100644 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreAndLoadRaceLogEventsTest.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreAndLoadRaceLogEventsTest.java @@ -174,7 +174,7 @@ public class StoreAndLoadRaceLogEventsTest extends AbstractMongoDBTest { @Test public void testStoreAndLoadDeviceCompetitorMappingEvent() { DeviceIdentifier device = new SmartphoneImeiIdentifier("a"); - Competitor mappedTo = DomainFactory.INSTANCE.getOrCreateCompetitor("abc", "abc", null, null, null); + Competitor mappedTo = DomainFactory.INSTANCE.getOrCreateCompetitor("abc", "abc", null, null, null, null); TimePoint from = new MillisecondsTimePoint(20); TimePoint to = new MillisecondsTimePoint(30); RaceLogDeviceCompetitorMappingEvent expectedEvent = eventFactory.createDeviceCompetitorMappingEvent( @@ -251,7 +251,7 @@ public class StoreAndLoadRaceLogEventsTest extends AbstractMongoDBTest { public void testStoreAndLoadRegisterCompetitorEvent() { RaceLogRegisterCompetitorEvent expectedEvent = eventFactory.createRegisterCompetitorEvent( expectedEventTime, author, expectedEventTime, expectedId, expectedPassId, - DomainFactory.INSTANCE.getOrCreateCompetitor("comp", "comp", null, null, null)); + DomainFactory.INSTANCE.getOrCreateCompetitor("comp", "comp", null, null, null, null)); DBObject dbObject = mongoFactory.storeRaceLogEntry(logIdentifier, expectedEvent); RaceLogRegisterCompetitorEvent actualEvent = loadEvent(dbObject); diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreGPSFixTrackTest.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreGPSFixTrackTest.java index f1adb9b63e6..078dcfd56fd 100644 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreGPSFixTrackTest.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreGPSFixTrackTest.java @@ -58,7 +58,7 @@ public class StoreGPSFixTrackTest extends AbstractMongoDBTest { public void testStoreAndLoadFixes() throws TransformationException, NoCorrespondingServiceRegisteredException { TypeBasedServiceFinderFactory factory = new MockSmartphoneImeiServiceFinderFactory(); AbstractLogEventAuthor author = new LogEventAuthorImpl("author", 0); - Competitor comp = DomainFactory.INSTANCE.getOrCreateCompetitor("comp", "comp", null, null, null); + Competitor comp = DomainFactory.INSTANCE.getOrCreateCompetitor("comp", "comp", null, null, null, null); Mark mark = DomainFactory.INSTANCE.getOrCreateMark("mark"); MongoObjectFactory mongoObjectFactory = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService(), factory); DomainObjectFactory domainObjectFactory = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE, factory); diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java index 720b868c6db..ff13f587b82 100755 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java @@ -627,7 +627,7 @@ public class TestStoringAndLoadingEventsAndRegattas extends AbstractMongoDBTest regattaStartDate, regattaEndDate, /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null); List competitors = new ArrayList(); - competitors.add(new CompetitorImpl("Axel", "Axel Uhl", Color.RED, null, null)); + competitors.add(new CompetitorImpl("Axel", "Axel Uhl", Color.RED, null, null, null)); Iterable waypoints = Collections.emptyList(); Course course = new CourseImpl("Course", waypoints); diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboardGroups.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboardGroups.java index 63f90f5e010..538815fa923 100755 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboardGroups.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboardGroups.java @@ -113,15 +113,15 @@ public class TestStoringAndRetrievingLeaderboardGroups extends AbstractMongoDBTe @Test public void testStoringAndRetrievingLeaderboardGroupWithOverallLeaderboardWithScoreCorrection() throws NoWindException { RacingEventService racingEventService = new RacingEventServiceImpl(); - Competitor wolfgang = new CompetitorImpl(123, "$$$Dr. Wolfgang+Hunger$$$", Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl("$$$Dr. Wolfgang+Hunger$$$", new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is famous Dr. Wolfgang Hunger")), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), - /* dateOfBirth */ null, "This is Rigo, the coach")), new BoatImpl("Dr. Wolfgang Hunger's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); - Competitor hasso = new CompetitorImpl(234, "Hasso Plattner", Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl("Hasso Plattner", new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is famous Dr. Hasso Plattner")), new PersonImpl("Lutz Patrunky", new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is Patty, the coach")), - new BoatImpl("Dr. Hasso Plattner's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); + Competitor wolfgang = new CompetitorImpl(123, "$$$Dr. Wolfgang+Hunger$$$", Color.RED, null, new TeamImpl("STG", Collections.singleton( + new PersonImpl("$$$Dr. Wolfgang+Hunger$$$", new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is famous Dr. Wolfgang Hunger")), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), + /* dateOfBirth */ null, "This is Rigo, the coach")), new BoatImpl("Dr. Wolfgang Hunger's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); + Competitor hasso = new CompetitorImpl(234, "Hasso Plattner", Color.RED, null, + new TeamImpl("STG", Collections.singleton( + new PersonImpl("Hasso Plattner", new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is famous Dr. Hasso Plattner")), new PersonImpl("Lutz Patrunky", new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is Patty, the coach")), new BoatImpl("Dr. Hasso Plattner's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); final String raceColumnName1 = "My First Race 1"; MockedTrackedRaceWithFixedRankAndManyCompetitors raceWithTwoCompetitors = new MockedTrackedRaceWithFixedRankAndManyCompetitors(wolfgang, /* rank */ 1, /* started */ true); raceWithTwoCompetitors.addCompetitor(hasso); @@ -169,15 +169,15 @@ public class TestStoringAndRetrievingLeaderboardGroups extends AbstractMongoDBTe @Test public void testStoringAndRetrievingLeaderboardGroupWithSuppressedCompetitorsInOverallLeaderboard() { - Competitor wolfgang = new CompetitorImpl(123, "$$$Dr. Wolfgang+Hunger$$$", Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl("$$$Dr. Wolfgang+Hunger$$$", new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is famous Dr. Wolfgang Hunger")), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), - /* dateOfBirth */ null, "This is Rigo, the coach")), new BoatImpl("Dr. Wolfgang Hunger's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); - Competitor hasso = new CompetitorImpl(234, "Hasso Plattner", Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl("Hasso Plattner", new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is famous Dr. Hasso Plattner")), new PersonImpl("Lutz Patrunky", new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is Patty, the coach")), - new BoatImpl("Dr. Hasso Plattner's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); + Competitor wolfgang = new CompetitorImpl(123, "$$$Dr. Wolfgang+Hunger$$$", Color.RED, null, new TeamImpl("STG", Collections.singleton( + new PersonImpl("$$$Dr. Wolfgang+Hunger$$$", new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is famous Dr. Wolfgang Hunger")), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), + /* dateOfBirth */ null, "This is Rigo, the coach")), new BoatImpl("Dr. Wolfgang Hunger's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); + Competitor hasso = new CompetitorImpl(234, "Hasso Plattner", Color.RED, null, + new TeamImpl("STG", Collections.singleton( + new PersonImpl("Hasso Plattner", new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is famous Dr. Hasso Plattner")), new PersonImpl("Lutz Patrunky", new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is Patty, the coach")), new BoatImpl("Dr. Hasso Plattner's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); final String raceColumnName1 = "My First Race 1"; MockedTrackedRaceWithFixedRankAndManyCompetitors raceWithTwoCompetitors = new MockedTrackedRaceWithFixedRankAndManyCompetitors(wolfgang, /* rank */ 1, /* started */ true); raceWithTwoCompetitors.addCompetitor(hasso); @@ -225,15 +225,15 @@ public class TestStoringAndRetrievingLeaderboardGroups extends AbstractMongoDBTe @Test public void testStoringAndRetrievingLeaderboardGroupWithTwoSuppressedCompetitorsInOverallLeaderboard() { - Competitor wolfgang = new CompetitorImpl(123, "$$$Dr. Wolfgang+Hunger$$$", Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl("$$$Dr. Wolfgang+Hunger$$$", new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is famous Dr. Wolfgang Hunger")), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), - /* dateOfBirth */ null, "This is Rigo, the coach")), new BoatImpl("Dr. Wolfgang Hunger's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); - Competitor hasso = new CompetitorImpl(234, "Hasso Plattner", Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl("Hasso Plattner", new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is famous Dr. Hasso Plattner")), new PersonImpl("Lutz Patrunky", new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is Patty, the coach")), - new BoatImpl("Dr. Hasso Plattner's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); + Competitor wolfgang = new CompetitorImpl(123, "$$$Dr. Wolfgang+Hunger$$$", Color.RED, null, new TeamImpl("STG", Collections.singleton( + new PersonImpl("$$$Dr. Wolfgang+Hunger$$$", new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is famous Dr. Wolfgang Hunger")), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), + /* dateOfBirth */ null, "This is Rigo, the coach")), new BoatImpl("Dr. Wolfgang Hunger's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); + Competitor hasso = new CompetitorImpl(234, "Hasso Plattner", Color.RED, null, + new TeamImpl("STG", Collections.singleton( + new PersonImpl("Hasso Plattner", new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is famous Dr. Hasso Plattner")), new PersonImpl("Lutz Patrunky", new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is Patty, the coach")), new BoatImpl("Dr. Hasso Plattner's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); final String raceColumnName1 = "My First Race 1"; MockedTrackedRaceWithFixedRankAndManyCompetitors raceWithTwoCompetitors = new MockedTrackedRaceWithFixedRankAndManyCompetitors(wolfgang, /* rank */ 1, /* started */ true); raceWithTwoCompetitors.addCompetitor(hasso); diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboards.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboards.java index d9fa5489325..0eae97a4c52 100755 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboards.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboards.java @@ -76,11 +76,11 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest { FlexibleLeaderboardImpl leaderboard = new FlexibleLeaderboardImpl(leaderboardName, new ThresholdBasedResultDiscardingRuleImpl(discardIndexResultsStartingWithHowManyRaces), new LowPoint(), null); Competitor wolfgang = createCompetitor(); - Competitor hasso = new CompetitorImpl(234, "Hasso Plattner", Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl("Hasso Plattner", new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is famous Dr. Hasso Plattner")), new PersonImpl("Lutz Patrunky", new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is Patty, the coach")), - new BoatImpl("Dr. Hasso Plattner's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); + Competitor hasso = new CompetitorImpl(234, "Hasso Plattner", Color.RED, null, + new TeamImpl("STG", Collections.singleton( + new PersonImpl("Hasso Plattner", new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is famous Dr. Hasso Plattner")), new PersonImpl("Lutz Patrunky", new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is Patty, the coach")), new BoatImpl("Dr. Hasso Plattner's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); final String raceColumnName1 = "My First Race 1"; MockedTrackedRaceWithFixedRankAndManyCompetitors raceWithTwoCompetitors = new MockedTrackedRaceWithFixedRankAndManyCompetitors(wolfgang, /* rank */ 1, /* started */ true); raceWithTwoCompetitors.addCompetitor(hasso); @@ -350,7 +350,7 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest { final DomainFactory domainFactory = new DomainFactoryImpl(); // create the competitor through the competitor store/factory here so that the DomainObjectFactory finds it and // resolves the score corrections appropriate - Competitor competitor = domainFactory.getOrCreateCompetitor(123, "$$$Dr. Wolfgang+Hunger$$$", Color.RED, new TeamImpl("STG", Collections.singleton( + Competitor competitor = domainFactory.getOrCreateCompetitor(123, "$$$Dr. Wolfgang+Hunger$$$", Color.RED, "someone@nowhere.de", new TeamImpl("STG", Collections.singleton( new PersonImpl("$$$Dr. Wolfgang+Hunger$$$", new NationalityImpl("GER"), /* dateOfBirth */ null, "This is famous Dr. Wolfgang Hunger")), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), /* dateOfBirth */ null, "This is Rigo, the coach")), new BoatImpl("Dr. Wolfgang Hunger's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); @@ -377,10 +377,10 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest { } private Competitor createCompetitor() { - Competitor competitor = new CompetitorImpl(123, "$$$Dr. Wolfgang+Hunger$$$", Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl("$$$Dr. Wolfgang+Hunger$$$", new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is famous Dr. Wolfgang Hunger")), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), - /* dateOfBirth */ null, "This is Rigo, the coach")), new BoatImpl("Dr. Wolfgang Hunger's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); + Competitor competitor = new CompetitorImpl(123, "$$$Dr. Wolfgang+Hunger$$$", Color.RED, null, new TeamImpl("STG", Collections.singleton( + new PersonImpl("$$$Dr. Wolfgang+Hunger$$$", new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is famous Dr. Wolfgang Hunger")), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), + /* dateOfBirth */ null, "This is Rigo, the coach")), new BoatImpl("Dr. Wolfgang Hunger's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); return competitor; } diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingRaceLogInLeaderboards.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingRaceLogInLeaderboards.java index 210c3031b0f..befdd06a53e 100644 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingRaceLogInLeaderboards.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingRaceLogInLeaderboards.java @@ -277,7 +277,7 @@ public class TestStoringAndRetrievingRaceLogInLeaderboards extends RaceLogMongoD @Test public void testStoreAndRetrieveSimpleLeaderboardWithRaceLogFinishPositioningConfirmedEvent() { - Competitor storedCompetitor = DomainFactory.INSTANCE.getOrCreateCompetitor(UUID.randomUUID(), "SAP Extreme Sailing Team", Color.RED, null, null); + Competitor storedCompetitor = DomainFactory.INSTANCE.getOrCreateCompetitor(UUID.randomUUID(), "SAP Extreme Sailing Team", Color.RED, "someone@nowhere.de", null, null); CompetitorResults storedPositioningList = new CompetitorResultsImpl(); storedPositioningList.add(new com.sap.sse.common.Util.Triple(storedCompetitor.getId(), storedCompetitor.getName(), MaxPointsReason.NONE)); @@ -346,7 +346,7 @@ public class TestStoringAndRetrievingRaceLogInLeaderboards extends RaceLogMongoD @Test public void testStoreAndRetrieveSimpleLeaderboardWithRaceLogFinishPositioningListChangeEvent() { - Competitor storedCompetitor = DomainFactory.INSTANCE.getOrCreateCompetitor(UUID.randomUUID(), "SAP Extreme Sailing Team", Color.RED, null, null); + Competitor storedCompetitor = DomainFactory.INSTANCE.getOrCreateCompetitor(UUID.randomUUID(), "SAP Extreme Sailing Team", Color.RED, "someone@nowhere.de", null, null); CompetitorResults storedPositioningList = new CompetitorResultsImpl(); storedPositioningList.add(new com.sap.sse.common.Util.Triple(storedCompetitor.getId(), storedCompetitor.getName(), MaxPointsReason.NONE)); diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingRaceLogInRegatta.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingRaceLogInRegatta.java index ede401bb4a2..31bc1d59e72 100644 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingRaceLogInRegatta.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingRaceLogInRegatta.java @@ -187,7 +187,7 @@ public class TestStoringAndRetrievingRaceLogInRegatta extends AbstractTestStorin @Test public void testStoreAndRetrieveRegattaWithRaceLogFinishPositioningListChangeEvent() { - Competitor storedCompetitor = DomainFactory.INSTANCE.getOrCreateCompetitor(UUID.randomUUID(), "SAP Extreme Sailing Team", Color.RED, null, null); + Competitor storedCompetitor = DomainFactory.INSTANCE.getOrCreateCompetitor(UUID.randomUUID(), "SAP Extreme Sailing Team", Color.RED, "someone@nowhere.de", null, null); CompetitorResults storedPositioningList = new CompetitorResultsImpl(); storedPositioningList.add(new com.sap.sse.common.Util.Triple(storedCompetitor.getId(), storedCompetitor.getName(), MaxPointsReason.NONE)); @@ -217,7 +217,7 @@ public class TestStoringAndRetrievingRaceLogInRegatta extends AbstractTestStorin @Test public void testStoreAndRetrieveRegattaWithRaceLogFinishPositioningConfirmedEvent() { - Competitor storedCompetitor = DomainFactory.INSTANCE.getOrCreateCompetitor(UUID.randomUUID(), "SAP Extreme Sailing Team", Color.RED, null, null); + Competitor storedCompetitor = DomainFactory.INSTANCE.getOrCreateCompetitor(UUID.randomUUID(), "SAP Extreme Sailing Team", Color.RED, "someone@nowhere.de", null, null); CompetitorResults storedPositioningList = new CompetitorResultsImpl(); storedPositioningList.add(new com.sap.sse.common.Util.Triple(storedCompetitor.getId(), storedCompetitor.getName(), MaxPointsReason.NONE)); diff --git a/java/com.sap.sailing.polars.test/resources/5O5Worlds14_Winners_Upwind4550.csv b/java/com.sap.sailing.polars.test/resources/5O5Worlds14_Winners_Upwind4550.csv new file mode 100644 index 00000000000..6abb4a4f5c6 --- /dev/null +++ b/java/com.sap.sailing.polars.test/resources/5O5Worlds14_Winners_Upwind4550.csv @@ -0,0 +1,8620 @@ +14.711552847861,6.94618228340744,-46.9340311996113 +14.6159918588954,6.54088070915254,-49.2612330883498 +14.572846357584,6.5026396664646,-46.8965213311156 +14.7444353798923,6.9322874671299,-47.1460377445613 +14.7365152168546,6.81940194001346,-49.836451949315 +14.859604607974,6.93289940368124,45.8738398488157 +12.8846305622534,7.19750052494341,46.5482238395806 +12.9710980182519,7.10907976170334,45.5744818336029 +12.7902426421164,7.40856352137361,46.2413323683829 +12.6793226254565,7.35335454068788,45.6104260345802 +14.4774114798141,6.85942658894508,45.9029742547059 +14.5252350152175,7.2230597061005,47.6681310665996 +14.5871031786617,7.37289061496789,48.5744103246467 +14.6690932641615,7.62430743906014,47.6657625188325 +14.6440907184735,7.53808866953584,47.9345620313256 +14.6725471186446,7.61941753496267,47.1864871387864 +14.5435407179465,7.23234736683786,45.8799011340583 +14.4476062246182,7.24133037922224,46.4747752203243 +14.3848711890206,7.31626756891012,47.934800688602 +14.298314277298,7.37882135938686,49.6332501084556 +14.2131158254716,7.87177889358349,48.366883048611 +14.0223385434789,8.11096304819554,47.8694995696142 +13.9316639443497,8.16773685988305,47.5673400911351 +14.1288090960312,8.02543803245828,47.8143830690836 +13.8265008813489,8.20702949224399,47.0528645243424 +13.3205511061634,7.74012536545492,48.0272724178019 +13.2282835167981,7.59319600041407,46.9131343824917 +13.1913069173927,7.54973352834865,47.3348067179067 +13.1841373584715,7.52228965193328,48.3124803358394 +13.1603164862826,7.48079260089842,48.5297198747742 +13.2548802673001,7.67808163296939,47.3589461623096 +13.1701369448872,7.48916982215104,49.8534748409756 +13.3104767086679,7.58377755479461,47.6711553914288 +13.1941730703085,7.5852898499925,49.6931717846005 +13.2627487428044,7.62711139030267,49.3492528590274 +13.3168810746174,7.53778753804486,47.7906594769404 +13.3917871530067,7.28697411658421,47.5401220733087 +13.3377053761841,7.46523241789488,48.3477535088292 +13.4090245128513,7.33927544385389,48.5329545445379 +13.3540379070854,7.37084903078733,47.7885198834288 +13.4336471240867,7.45364121539626,49.012524828009 +13.4249819954801,8.03096494195981,47.5696822249686 +13.4179809913144,7.91872599527293,48.4721319254204 +13.4179141511608,7.56075064782774,48.8985621556744 +13.4232659050506,7.75999963617433,47.9305235866418 +13.485750959733,7.99079110002143,45.9824161006373 +13.5152791758323,7.88919678185287,45.3475195495668 +13.4551061813851,8.0709073530278,46.7738364443917 +13.4971310135415,7.61468155991288,45.0982504994909 +13.3903153624206,6.86671341588622,45.9386574705885 +13.4353868702123,7.25144578172176,45.3480481184137 +13.3776476128705,6.5149128644851,48.0376223672559 +13.587874997171,6.94626199645777,48.771044908741 +13.5177502054129,6.87471832589357,49.57668498774 +13.6705548287336,6.93858713293373,48.5396930010053 +13.8120704074811,6.95252509016923,47.6362735225956 +13.8627831928314,6.89186960263324,47.0777962611198 +13.7688647890332,6.92990410654981,48.0807577260838 +13.9128653391672,6.83694402383334,46.1311599373438 +13.9637648618027,6.81232454249628,45.3582707399308 +13.954161889712,6.8938016175606,45.2152939191707 +13.9412049508927,7.04471679995682,49.7736887171483 +13.6587613839438,6.42586570325846,48.4732456985365 +13.4643032357113,6.27635073418743,46.9857673657815 +13.5415613020495,6.32663463334353,47.7620131218719 +13.430860347109,6.26033546826343,46.0203338673301 +13.3835202726599,6.28591106006682,46.5056597704459 +14.5953705572204,6.85712505176878,-46.0829246846163 +14.7649301317119,7.16860163493274,-49.2556374620174 +14.7498147848315,7.20443968485969,-48.5892917005523 +14.7213305868838,7.19471006764918,-48.5994801193325 +14.6586114611997,7.12690397125667,-47.5538873657252 +14.6057701803754,7.16534471763984,-47.0098565437128 +14.5341737582783,7.05316891140007,-46.6085509659574 +14.4278908256173,7.05454222894002,-45.8163075935542 +14.3457111705823,7.05882876643689,-46.0952119100189 +14.2386163885391,7.12682558029447,-45.4888153898683 +14.7295031850289,7.13314997303313,-48.8124801253549 +14.6413492038667,7.02869379610399,-47.3624497191194 +13.1835601847586,6.86692968583657,-45.6332289094956 +13.2293310275873,6.66178172901478,-46.6563366646234 +13.2831417554694,6.62148656221257,-46.8043329997902 +13.3152947218707,6.68065981993835,-47.7274723310016 +13.296591963554,6.5604981311684,-47.5170254048104 +13.4096423821086,6.95451507003495,-47.8334468567109 +13.5025730608028,7.06362292257368,-47.9299767281273 +13.6188033329141,7.1715187154922,-47.9458687072686 +13.8857449274808,7.21246216149313,-48.9043944902334 +13.7588121735686,7.14531368142722,-48.4721247946916 +13.9868979893651,7.15777459665182,-49.7513260721817 +14.3042984425327,7.43301025746943,-49.8781295086319 +14.3108997765479,7.47228444897473,-49.2701944502014 +14.3133963979744,7.51166742003783,-48.4367527681516 +14.2725693624838,7.68542707423661,-47.8958006323538 +14.1950479360709,7.28893908453086,-47.7504230593656 +14.0599863033057,7.15816195881581,-47.7021498369124 +14.0078196936853,7.12005536095119,-47.3848195634394 +14.1654611126178,7.0251112398412,-46.4190270103137 +14.1147051075126,7.0328783046043,-46.9269877856723 +13.7138705897593,6.98831239871708,-46.432532775534 +13.3016184523348,6.8213111141013,-46.1793467664636 +13.5778806743806,6.94370677877869,-45.8984039251234 +13.2284629736196,7.00667647914802,-45.433167565947 +13.4313160379083,6.91956052807993,-46.3021967415093 +13.2002372382511,7.10009549061528,-46.1465869717252 +13.1855942659699,7.00963952937872,-45.5845836628178 +13.2177207191877,6.97262404118084,-47.2637503467996 +13.2189525709183,7.03980177229186,-47.0169517682449 +13.1910991529492,6.94377294066074,-47.9383852576578 +13.1040522554119,6.99784442771997,-49.3114730527894 +13.1650583175154,7.03488953496435,-49.6489077931608 +12.9140463921829,6.76008417949426,-48.0894284142373 +13.0149816458219,7.00595081141863,-48.7732763914829 +12.9478076962767,6.79045017937601,-48.0367806578975 +12.8453868763154,6.8685489543064,-45.9327082723102 +12.8467783807393,6.77407782085816,-45.4923230336672 +12.871582747855,6.96266251248797,-46.4306691952473 +12.8637702014519,7.35971398265981,-45.5640272716167 +12.8676537312542,7.33259541968174,-45.6918915236947 +12.9016813289435,7.2666689287084,-46.9136121344247 +12.8574732168599,7.21900003961627,-45.7384911452388 +12.94594865354,7.10943776804579,-47.5737907094012 +12.9830870364918,6.88301018730776,-49.4937084785899 +12.9932891129371,6.8237333806282,-49.0934943784602 +13.2194902350466,6.63775778639742,-48.4956865888111 +13.334754309999,6.44937528803907,-47.0947758136156 +13.2717327700127,6.52776021029062,-47.7239610296339 +13.4140386747454,6.41135211561589,-46.0760091237819 +13.4908339054426,6.21995405216969,-45.5059347712633 +13.6808837229007,6.63931003087858,-49.5258187410111 +13.5422758178355,6.6972935253648,-47.3673770297868 +13.6197042848199,6.67457057233847,-47.8948241872071 +13.838789665176,6.08508047966845,-48.2613929426159 +13.9515322171774,6.11987340820209,-46.240985044909 +14.6063490765376,7.04426287938917,-47.9991207182136 +14.7403057912027,7.31430258551916,-49.73573548114 +14.6643520689918,7.19159720683108,-49.3886223473704 +14.8284199155944,7.31803606245404,-48.2490156756994 +15.029812721723,7.32385282887637,-45.314060833098 +15.4509703643605,7.50807668635425,-45.5198424700044 +15.5337189020774,7.5375504303152,-45.9088186467321 +15.587481790515,7.57047094954184,-46.2143442999794 +15.6333964423647,7.53461220663252,-45.5331016818681 +15.6384545796223,7.09473926966968,-45.3969655193814 +15.6162886721351,7.05164566528859,-45.6370459942958 +15.5894592372368,7.07419357414695,-46.1463482866243 +15.5592504612216,6.97693234003942,-46.5236378339546 +15.6162883221431,7.13219795535457,-45.9401333694421 +15.5384044189672,7.31338767682532,-45.045805725608 +15.4604996031454,7.3883042726355,-45.2242616643834 +15.3493852726577,7.5048476338701,-45.0320368193982 +15.2704137404386,7.52285530178016,-45.3711757981636 +15.2278871755199,7.59413577447291,-45.9539699904133 +15.2737175359708,7.0317726970036,-47.0139710894914 +15.233350281191,7.21560000783488,-46.8232342926184 +15.2735285135603,7.10306607397733,-47.2726424391102 +15.2646318072471,7.10775666627298,-47.7039485330362 +15.2631827276419,7.1674959006977,-47.7511831129844 +15.1785074448622,7.18347443327854,-47.5642660608181 +15.2188713912465,7.12777345243463,-47.4344511548554 +15.2318691853144,7.18297873856512,-47.6275221538546 +15.2520220575431,7.21817116226124,-47.4368050946444 +15.2625840576869,7.27319395988313,-47.3175710957428 +15.2693757150283,7.41970263611756,-46.7989580777256 +15.1629135466246,7.65241644258317,-46.325557187646 +15.1265850063555,7.64124769286392,-45.6421962374348 +15.2268472474199,7.47482462886036,-46.8128798681359 +15.2099576283779,7.53900683718088,-46.7673691481655 +15.0795696158007,7.30062878985305,-49.4035032260771 +14.4456792095225,6.31807200619612,-47.2020786600334 +14.5103869953463,6.17173385520138,-49.5502856023752 +14.2283190506564,6.59975734675628,-45.4885337004087 +13.6868866837931,7.42632748659588,-45.105999877109 +13.7115307916983,7.49614725940392,-47.2224881778091 +13.7566997815598,7.48826953192901,-48.8646878777822 +13.7327909855121,7.01807295090209,-49.9548586222077 +13.7466241748613,7.14044434801785,-49.9150746750602 +13.721234523127,7.41612190694375,-47.934953175104 +13.7413830811542,7.27048868464273,-49.3687745080594 +13.6641200932172,7.31430120976571,-46.8003561279305 +13.4890044348565,7.05426518328569,-48.6515286420938 +13.5461476719113,7.11070254291513,-47.5182626663723 +13.61022136266,7.23913437476483,-47.1255694200099 +13.0018546438582,7.15669702872004,-48.7072159298528 +12.9584753746032,7.26112628582555,-47.6561832925196 +12.9579485421835,7.31482188702569,-46.2297876125993 +12.8796084464971,7.08067503355049,-46.1399913529845 +12.9015688884354,6.90436035616661,-47.0918870264287 +12.9304681021657,6.68747155419885,-48.1317779044753 +12.9597932242813,6.63551526842123,-48.7453943266286 +12.9813788700025,6.57410068858411,-48.4505808321224 +13.0144706410517,6.63481700530826,-48.685332426311 +13.0371348582893,6.75244327325197,-47.0827523285967 +13.0371361944098,6.69309522888522,-46.4304304898026 +13.109948167039,6.69436207457333,-48.2215509897433 +13.0564971935708,6.75675576619237,-47.8239363576636 +13.1714861090192,7.27990946218518,-49.3925271351103 +13.1561967234659,7.40356080758035,-47.6884346147856 +13.5825607436908,7.12759490057729,-45.3375300918372 +13.6757047615512,6.59683058167509,-47.9313508547503 +13.9662046235651,6.31800284930556,-49.418726063111 +13.8294930481737,6.5074163164671,-49.6287345538971 +14.1848942405178,6.25013622044927,-49.9492041390762 +14.591748554489,6.53614262085633,-48.6217721658753 +14.7499874486082,6.71694036040075,-49.1854024245538 +14.8766694107694,6.8410792443764,-48.2092599572128 +15.0071314508536,6.94010016465711,-48.511966676181 +15.1229118048438,6.99183723194362,-49.2629565656183 +15.6471629534684,6.82432735679438,-46.8015296526396 +15.7190410724209,6.91941700248639,-48.2696641545066 +15.5848183548721,6.75797523136206,-46.9157977760487 +15.3765636822159,6.62371841360385,-47.7034837059605 +15.4869315521833,6.6397974524847,-47.6372693395932 +14.8170869023942,6.49509464953368,-49.6750709470182 +15.2747810069402,6.61977292022613,-48.2030238706527 +15.1696187542792,6.51732583285767,-49.2886911941079 +15.0587639221396,6.57748668004891,-48.5238666111601 +14.9197389936205,6.59567679210884,-48.3206847591833 +13.8632676039681,6.71414289332069,-49.8916385341516 +13.8995790628321,6.85144485267939,-49.2600093273842 +12.404977919362,6.65235510891555,45.5347728230634 +12.3801489807265,6.65627258208074,45.8041038829186 +11.8917178310978,6.05660357353104,49.1752265706924 +12.6677047735165,6.21829061948803,49.6906075578739 +12.7737515246081,6.25928650410134,49.7965121526927 +12.6185364896315,6.81684620803896,49.4076774323347 +12.6475262065448,6.848079938338,49.6820279325123 +12.6654052223146,7.05113947496727,46.5752258004084 +12.5768235637676,6.71705278326496,-48.1834090219133 +12.8238447902133,6.75504830768144,-46.1554061314817 +12.8600167715779,6.70949596657113,-45.9144201879356 +12.7624227042745,6.7611275306046,-45.8100988343795 +12.8711032353672,6.6950120494505,-47.5129694980938 +12.8762230198742,6.75508455128574,-46.6071520656831 +12.8230266908866,6.99212053438032,-45.7600542800887 +12.8288439669796,7.02945826723096,-46.0454545670272 +12.8288792319858,7.02998902044757,-45.572528592492 +13.4359834774773,6.894545638977,-47.473170692104 +13.5113742193938,6.93829300266345,-47.9646703369841 +13.744489679729,6.95893098232894,-49.3021094452656 +13.5827796111933,6.9189215222822,-49.124383178441 +13.6681707786627,6.89525162109361,-49.9152401061033 +14.3253046437324,7.09162716456454,-46.1246069748079 +14.5334425351666,7.15796713063406,-46.1674480148918 +14.4654470987262,7.13043110325981,-47.6203683032456 +14.4114698826285,7.03539789557043,-48.7602174536877 +14.8906132107518,7.28047844494593,-45.2938572828548 +14.9500549947699,7.27342285849742,-46.1904269031452 +15.0489205676389,7.31049291491253,-46.9503244719038 +15.0243649979588,7.2864790837202,-46.2851702602222 +15.0826044667176,7.34704138861661,-49.353988164794 +15.0815228444912,7.40825683047985,-48.0872337223198 +15.0776583396078,7.48654725753435,-46.8759451440999 +15.0845790344323,7.58262346849416,-46.1400969166312 +15.267442593594,7.62678606470899,-46.1289481612869 +15.3812762444534,7.62805268091487,-47.5284712332699 +15.5869267650758,7.40040610475065,-49.5735648687691 +15.5174082102141,7.53522247046115,-49.4588672585091 +15.5773599137223,7.2745780695025,-49.9710116446288 +15.6129438571348,7.26298806446619,-49.2174953725039 +15.6483929370885,7.24674332796299,-48.2069474776499 +15.7108264951503,7.30033221120015,-47.7689898291797 +15.7671579851365,7.48281923125894,-49.2751511049817 +15.7307341874833,7.59392726606696,-46.3732707787552 +15.7436811581868,7.60258451644006,-47.7795768746362 +15.4588548333764,7.87409375429705,-45.4412318677832 +15.3869526728951,7.85906570158903,-46.7198053120556 +15.3034282549769,7.9602757988749,-46.1142165707107 +15.2554974041565,7.94117109052962,-45.7323572464171 +15.2114064112272,7.84248270819316,-45.2388971218695 +15.1658190122156,7.85419949954236,-45.0808525104715 +15.2897965851397,7.91540931260313,-45.4097495599005 +14.9833841545024,7.7876514826042,-49.86614001065 +15.0971690301096,7.76538675712188,-47.9641215507222 +14.1217137483992,7.93277783902597,-49.3019651913443 +14.3390070104803,8.02407857775748,-49.0982971612203 +15.060004235812,8.10471311875845,-46.9144573729985 +15.1061173910151,8.09419537616321,-45.1201762740492 +15.0047547835928,7.70839817871016,-45.8665442570716 +15.0386179700666,7.77991517602384,-45.523447889705 +14.9739499280467,7.64938302183054,-45.5847319884975 +14.9765264513354,7.58392996644158,-45.027650599265 +15.0030130042239,7.39587571941684,-49.3062259855153 +15.0246829635027,7.17750248759549,-48.1129126130234 +15.028681111368,7.1153678094697,-47.0643382826768 +15.0617933882764,7.24545988130944,-49.4328899850541 +12.2306386051028,7.89395114390148,48.9461875234444 +12.0340505661188,7.92903242373883,49.3730502490857 +12.1770960646286,7.89952597399336,49.7374745867589 +12.1051301700528,7.92360841881134,49.2071564283372 +11.9752075892959,7.92841623508044,49.8262476740069 +11.9431590303889,7.92579967725081,46.0279400526771 +12.6227458681005,6.24517401899082,-45.473200574421 +12.6847841676021,6.35754779406096,-49.8176276402118 +12.9973760771393,7.01186882448989,-48.9499221228461 +13.3715900452409,6.60167928773874,-47.4664870202181 +12.9429123732044,6.64654515830395,-48.9386056569546 +13.354519409844,6.61822092003415,-47.0739726901748 +13.2710511235493,6.59306859291806,-45.5213868703125 +13.3233751011859,6.53468922403778,-46.4339202211515 +13.0233619429925,6.77249036383824,-45.5571403370861 +12.9389357794915,6.84476092652796,-46.4824129078599 +12.837947159763,6.90258252918889,-46.7808068986242 +12.7156106148799,7.09650692793771,-47.3363503637389 +12.467818373814,7.32517890083306,-46.9712013603837 +12.5783747361823,7.25977929505245,-47.4872906871429 +12.3694276059204,7.35334542036259,-47.000021718735 +12.3032301586987,7.4961819157028,-46.8988975059103 +12.2580494339561,7.32896521162645,-46.4156287274912 +12.2329931555151,7.31669512440123,-48.7022434560519 +12.1686899074415,7.1823558956209,-47.4888833038057 +12.1537055284882,7.24752510912063,-48.7146381844803 +12.1731929729351,7.21902067026705,-46.035538779698 +12.1700090743165,7.24531641519097,-46.9681141618943 +12.163695171893,7.30220975890532,-46.2902735808173 +11.199343438102,7.20583044295896,-47.130453521403 +11.1193785963718,7.12379963297734,-45.5554910250023 +10.5588745559977,6.70480458982114,-45.596701561023 +10.5238274247705,6.5824053827557,-45.560733685325 +10.5075303517988,6.56522019018991,-45.3675346223885 +11.0067745379264,6.43901248797274,-47.2687896402243 +10.9254017678361,6.50430715527373,-45.6742323572842 +11.0860174407236,6.2630668260237,-49.0628870265931 +11.2758594138556,6.20202996195265,-47.7150417469543 +11.1563619310433,6.24450631316083,-48.409579605363 +11.4428963682184,6.17940012695773,-45.6686558493989 +12.4133122985361,6.54936369863328,-45.4490700653168 +12.5375371962072,6.67987806190764,-45.3146062120132 +12.6994087196443,6.81170790297343,-45.4559133287821 +12.6296685213439,6.79821348146887,-45.0909248131638 +12.7102618948695,6.75545444276341,-45.4592420480073 +12.6646607696265,6.69329007368221,-47.3658154017329 +12.5881489955463,6.56268076183713,-48.1937000454791 +12.4218830995741,6.37609285388001,-48.4700181039778 +12.3675583930644,6.23266568051814,-48.796224410641 +12.4866736571346,6.45180545722542,-49.2609452302725 +12.3388376515066,6.09513424385224,-48.7793044253394 +12.3331252351487,5.91976891539028,-48.2303937229552 +12.2161264976244,5.80154322230025,-47.9816844329467 +12.2582302790877,5.90132888689207,-48.5936897749184 +12.1605984945931,5.63274198436091,-47.5439535256859 +11.9012532380336,4.93706579786348,-47.3102732145832 +11.7824896558941,4.89520123849967,-46.3518359250264 +12.0345301539331,5.06504360006739,-47.7901725301013 +11.6459769019182,4.86005178399657,-46.5223710077186 +11.436756974198,5.2149122521204,-45.34306055518 +11.4704521381331,5.31202624383632,-47.3091729671047 +11.5686204312178,5.35381534850587,-48.8370367682324 +11.9012556277369,5.12044711226185,-46.4685930816304 +11.6822836224183,5.26804429306482,-47.9345235542336 +11.7959748961933,5.21842752435636,-47.0886786160544 +12.020421888591,5.15061206299556,-46.3901935660715 +12.1478549194643,5.1436930306306,-45.6646584901816 +11.7044283502439,5.48816079014622,-45.9397598489062 +11.5398179155825,5.33651621725464,-45.401579118318 +11.6307052700703,5.37066217759708,-46.1167779812245 +10.5325178794661,5.87932444373674,-45.8029280197017 +10.6346656890565,5.88986613417924,-45.5527749373304 +10.3747880743364,6.55923157877322,-48.459207381755 +12.9193588357234,6.82307922031555,48.817188342488 +12.9230625232113,6.53670020745292,47.5113334075808 +12.9281189806034,6.41271017663053,47.5812590858073 +12.9413517973302,6.22245666435608,46.8974785916035 +12.9436629573326,5.98801946281497,45.4634195026869 +13.0287771937712,5.61356628571663,48.2534797387981 +13.15959440014,5.83536256132368,48.9217907558271 +13.0140215390148,5.60333327718287,47.5969346844768 +13.0801901593605,5.68475535429764,48.9774818552235 +13.2040863698572,5.85564094322689,45.7887175491317 +12.8667437706901,5.70140312114528,45.0848155382502 +12.9775051364043,5.76395368870932,45.3041358663923 +12.8046206705256,6.0561217832098,48.0610178058477 +12.7696929995046,6.12407936067944,48.6272668149565 +12.6338150122335,6.29875351956607,48.4712567489507 +12.6448017226106,6.30968091916708,48.2050564383395 +12.7156375635223,6.26806219290099,48.6953473806275 +12.6506525349602,5.9175809786141,48.531753439373 +12.644132106089,5.6364967900919,49.1235661462069 +12.6431965840304,5.7286705218746,49.3372603226211 +12.631381521235,5.56980349914976,49.265269639712 +12.5735361223676,5.61447647304748,48.2436799664376 +12.5646307790017,5.92136104980955,47.1056017191253 +12.6590875015439,6.14503367031352,49.1149034036707 +12.584739018472,5.98314570755475,48.8352788508365 +12.9262899006808,5.56519164650131,46.2745937616238 +11.96479166415,6.39977771662052,-49.371472671434 +12.1692675955274,6.34589792610899,-46.4064079311609 +12.0411262601237,6.38609363126879,-48.114550718744 +11.5904329176218,6.44087065406149,-49.9502379400151 +11.5860311839567,6.55395903044081,-47.5494919186564 +11.6065518582997,6.68179116367616,-45.6841872673374 +12.790873880634,6.4235135597154,48.6769832644915 +12.6501292179139,6.40092908877314,48.8248279976286 +13.5473045020963,7.47190911974559,49.592935639906 +13.6012487038201,7.59829796888071,49.0847338937798 +13.6490599005934,7.64680754991332,48.9811762128448 +13.7009492107932,7.69315694931139,48.5619742857834 +13.8034589583566,7.4625109220587,46.9761868523208 +13.8348406496215,7.58722152803218,46.6790744932307 +13.7679124629624,7.48573699851039,47.5188451511194 +13.7952114642481,7.48110135047142,47.3650134848473 +13.8173832258847,7.52767751047908,46.5428271599964 +13.7806876361239,7.38516317719336,48.4486995015173 +13.8220620505937,7.41725001815485,47.0354566517927 +13.8175581373906,7.3932993552293,46.7396442007689 +13.6926216592089,7.38984184818135,48.4934474729608 +13.7214884606766,7.36118139448745,47.7121224903782 +13.6478451600662,7.69207912065963,47.834535652255 +13.6159610995884,7.77847710105306,46.6353985381099 +13.6023534036337,7.83383648428792,46.2204873659886 +13.3747724542501,7.94091727121758,45.5725351391117 +13.2767675364189,8.03794144950192,46.8877321396606 +13.2944308043535,8.01040021991424,46.4849461812845 +13.3022650007327,8.17639251325926,48.9205992196074 +13.2751983087689,8.13615373723926,47.8459435642243 +14.2875886814618,9.00765130940429,49.3389759399447 +14.4327147544483,9.21969688008126,49.7869181104999 +15.9701051372785,8.21477922524831,49.7668092266216 +15.9904996481756,8.3002140059692,47.866515619659 +15.9903421156477,8.37270970100538,46.9679716062477 +15.8921708888226,8.56461066357729,49.8407705613099 +15.9605688922479,8.61498512218061,48.3548314602099 +15.9420219677407,8.58597436442098,49.1418767259175 +15.8117371942247,8.33303687905251,49.5676144791914 +15.835485291999,8.48137240713948,48.7018250656498 +15.7862962186312,8.2418700458039,45.976277087418 +14.4340175455992,8.25743746465377,45.5722313298047 +14.4056502808149,8.32124895423551,45.3355901088911 +15.1945968557163,8.11901600659021,45.0044558957076 +15.2525416504869,7.9937014708694,45.6715098729898 +15.3408738226935,7.78687917376312,45.6539539384234 +15.4329870660995,7.75494367843455,46.6777519539866 +15.4009608331217,7.71589009710907,46.475611602404 +15.4007714454253,8.25342627189963,45.8083651461499 +15.3696840499922,8.67971349591705,45.1357823038332 +15.3480400839854,9.03901426368719,45.0139367194123 +15.3661376757315,8.87743276426036,45.1509320366228 +15.3850447416373,8.54393556157044,45.0675365864278 +15.1456784126813,8.85950257689531,45.3930011187835 +15.1416758791705,8.89779423870479,45.683419254903 +15.0793868846653,8.86163377607758,46.4725847360733 +15.1129863751148,8.93133877782643,46.6983177900313 +15.0394633398832,8.82802498918976,46.6305210781332 +15.0072921165244,8.60915507688734,46.9214564502924 +14.9544737714076,8.45752963582419,46.6351074677581 +14.8676891574554,8.1004040477648,49.0128343062955 +14.4028787192027,7.01044697474102,47.3043074972805 +14.561970414273,7.09622155504914,49.6921333341692 +14.4870854213217,6.97329113843857,48.7752779041243 +14.3348332983107,7.11481684944755,47.1551903123659 +14.2874631405281,7.215867387082,46.9590645103526 +13.6989453815796,7.85735659517908,49.7922112049826 +13.8654393447285,7.2699562902507,-46.8183228018507 +13.7479241738977,7.36782215964642,-47.5424863355011 +13.6293797923455,7.53321979225435,-47.9328483867954 +13.6480078395498,6.36673954491388,-49.0722913292181 +13.7060900645954,6.48722011431678,-48.9432987138328 +13.6280013814949,7.20177823793779,-49.8160754019011 +13.5655282193859,7.36935072643142,-49.1457731575472 +13.5989724371025,7.31207054843867,-49.3798352603554 +13.5181191468114,7.44268356074134,-49.5600171907737 +13.5075637852442,7.52797226338573,-49.8719671039649 +13.5225055800264,6.88810085073038,-49.7103074429754 +13.5314564807472,6.93391805881357,-49.4227356205795 +13.5386124603002,6.90406554115897,-49.1549429089059 +13.5187227608413,6.9311166600204,-46.8435577768743 +13.5214112000872,7.09630703560764,-46.0327119778041 +13.6296184688419,7.40759537639829,-45.101350418627 +13.5483554217206,7.1250056695422,-45.09532823164 +13.6952014997996,7.51966225557909,-45.9387964226676 +13.5802253918038,7.52455289233533,-48.3675961125074 +13.6367987385586,7.57244733277463,-47.5702406320829 +13.441049517755,7.44895503175863,-49.5116337700394 +13.5193286496451,7.52237316962606,-48.738160775441 +12.9627321851943,6.84523497931308,-49.2407344838959 +12.847568212234,6.74774883216123,-48.9667033062512 +12.7044316392772,6.96025296455415,-48.1637789522155 +12.5882856342073,7.08040491026928,-47.4085603343247 +12.4693721397158,7.21004474426118,-49.0120277465791 +12.2584254398724,7.24655653760971,-49.6864296448096 +12.3898795046181,7.26764183270387,-49.3009326324702 +11.9538732755549,6.921959858537,-49.3574738247703 +11.9510006870642,7.03458106943864,-49.7695053624856 +11.9511767733913,6.95152781715587,-48.8009594303651 +11.961007579038,6.87668861106913,-47.5518985377571 +11.9716042149455,6.87307639268973,-47.3196262510945 +11.992082766513,6.87140969740335,-47.7409767158153 +11.9827788039156,6.74238199253243,-48.2556345475599 +12.0049672485191,6.91093658728864,-49.8368394648254 +13.2245666711028,6.77744347678659,-49.744171049169 +13.1632302366358,6.67380571045479,-48.5936701643936 +13.1960078845426,6.74885665611651,-49.1742753711129 +13.1056706915054,6.91034022094616,-47.4887250488168 +13.0634654551436,7.0457270613255,-47.2455634805168 +13.0265042752167,7.10545246404683,-46.8544043895403 +13.0020259192744,7.18213591651471,-46.2037092087219 +12.9947190963552,7.34077529324432,-46.2047129693863 +12.9116143757669,6.8692910758867,-49.8430775612113 +12.8023834202825,6.78852093475257,-49.6371919223414 +12.7523169048155,6.68749697936127,-49.2547324576499 +12.7897122181353,6.65124328998181,-49.8056294799128 +12.697792966829,6.69528154571691,-49.3025541082324 +12.5887092321475,6.80549169840125,-48.6531934391951 +12.6363200018683,6.76486186002748,-48.939393280176 +12.6056400292243,7.09362960078144,-47.0968749108889 +12.6446947998505,7.16908421532253,-46.9793559422637 +12.7665079804175,7.22449120027522,-46.0979165061942 +12.5880491675225,7.05023266476371,-48.0524958335461 +12.7137687937226,7.2801399193769,-46.2816179650522 +12.8373840410921,6.5480430306573,-45.2148968507158 +12.7801616054072,6.72434017690766,-45.0898260114391 +13.0666181365066,6.66847396328835,-45.7372535166497 +13.0746579813437,6.76560895151079,-45.2604375672145 +13.507340201076,6.86086874819646,-47.5054467298043 +13.5737813189065,7.01737092373534,-46.3240311202159 +13.4285544642711,6.74116913066556,-45.689421914508 +13.6035172951043,7.18466537868012,-47.9692852193594 +13.6168049373862,7.33090711716151,-49.6531400933735 +13.6267045545706,7.33385190058407,-47.3701853168912 +13.6200358849243,7.29551011781252,-49.97671164716 +13.6673042604887,7.26021133721675,-46.5963251094859 +13.7192255183877,7.27368359653293,-46.7111501210745 +14.1541825478947,7.50598158530145,-45.2325356099421 +14.177865179574,7.62259337395658,-46.3076230041003 +14.1464264223486,7.42886687054577,-45.503807729611 +14.114584011295,7.24500844849886,-45.2171994635079 +14.0747477430526,6.9079975745986,-45.4082979777695 +14.0047374454853,6.3266055539162,-48.7190495087929 +14.0168871758112,6.33178004392784,-49.5857596033148 +14.0032378810005,6.82388629348792,-49.5737055336083 +14.0141451256102,7.18129338945674,-48.7431984088119 +13.9828871891512,7.19309821460724,-47.233545267714 +13.9866475103231,7.14010698289785,-46.0070938539757 +13.9924470705486,7.29443854897376,-48.7940788043965 +13.9826715002344,6.90533638136347,-46.1893118594297 +13.9943537949624,7.06149569297401,-45.913957728929 +13.9471877635377,7.02358365048334,-45.1311694589958 +14.0402087958688,7.58705139091941,-48.2173253046018 +13.9583935966164,7.20660415389648,-47.0671057364891 +14.0990120738357,7.67635849161072,-47.3234835948024 +13.9692603416543,7.39941485880148,-48.0159801825394 +14.1601324066551,7.64704808543128,-46.0045302042014 +14.5271889564329,6.93713267706407,-46.9339939373515 +14.4233781311166,6.87743582223267,-49.2297114122312 +14.35164462702,6.84878757659374,-49.4041798896812 +14.1882125456803,7.04746197890988,-47.9117913134149 +14.2567508296641,7.01887614257059,-48.9429505852974 +13.9852811061917,7.38087020027584,-45.2137374598551 +14.0978204107448,7.23837317918282,-46.4785205442838 +13.2435668565707,7.12577604890377,-46.004771365029 +13.2136990129301,7.15793540163024,-46.2300255163811 +13.0476022589339,7.05102972356237,-49.1423134745449 +13.0182460489844,7.13720312900963,-47.7655949970296 +13.0123402136909,7.24022016505188,-47.4521155203493 +13.0749153865685,7.53093385111603,-48.4465163073664 +13.0097538299411,7.46531985493076,-46.8767997051588 +13.0161251793979,7.48033065633022,-46.4929856829291 +13.1383043727548,7.54142594307826,-48.635468190343 +13.2031531397998,7.4482560923638,-47.604634585841 +13.2756644706909,7.41978726024022,-47.9993471634259 +13.3710703134202,7.07669264224065,-47.2523324375997 +13.4454767564612,7.04201979728395,-47.2409703575321 +13.4526912326745,6.88543970318689,-47.0542551612081 +13.3134815782893,7.11309403429589,-47.7912413962157 +13.4621874055316,6.91595244269478,-49.7783968386558 +13.4362684901976,7.09153554815971,-49.8677580028313 +13.1885620434805,6.78415012910262,-46.0736025629681 +13.279357012408,6.96465983622911,-47.6694158987448 +13.3709622221985,6.9385062123761,-49.1065626525472 +12.7970914651826,7.28678015587713,-47.4021796934858 +12.6637095145069,7.31095313634385,-45.8181806372112 +12.7148816086338,7.35612208416369,-46.2959348773159 +12.6102558075471,7.28962302275103,-46.2297982517715 +12.556891306482,7.35541137454493,-45.2188854807628 +12.454530702395,7.49723675984141,-47.9300697385172 +12.5039632609676,7.54111051302609,-48.0504636498734 +12.4123858414538,7.59901042842858,-48.364657572773 +12.3799663007724,7.46300047581238,-48.0465137438785 +12.4227259139551,7.52703838478476,-46.481422152188 +12.4816993129862,7.29796050217749,-47.7452095356745 +12.522748872886,7.1179593199622,-47.2751155564761 +12.5102023549287,7.15031911353045,-47.6397598922675 +12.554772825868,6.95168705577629,-46.5187241679384 +12.6213280039512,6.90085531244309,-45.4779427390721 +12.718684017401,6.95298645275491,-48.1948689894994 +12.8487771184769,6.9960737323336,-48.864590694901 +12.9110686887353,7.00880377174243,-48.948313734407 +12.9447713706467,7.06579882488142,-48.0041689882719 +12.9783590125775,7.03259746405873,-47.4048526858501 +13.0119956846857,6.81295523272318,-46.6183141574735 +13.042140980392,6.78898915269307,-47.5393920008932 +13.1024778585334,6.72224616673561,-47.2028206612464 +13.1705734372361,6.6585945481623,-48.1746033299742 +13.2205453935395,6.71751757865184,-48.4756278768834 +13.1626330175658,6.59805338472801,-45.292416286105 +13.1776328126281,6.6116491935475,-45.077436823182 +13.1979401072089,6.62262205774984,-45.6502229956785 +13.1859316135656,6.84602807263967,-46.5515802632962 +13.1470285590639,6.77690378348148,-46.0604585857707 +13.0754482630627,6.77941580288088,-46.4046638737078 +13.050347000451,6.61840494385922,-45.3184806118634 +13.073071675431,7.11851803963791,-45.0757854425535 +13.1450006290287,7.70245080807772,-45.5312312177134 +13.088273141094,7.37510199348974,-45.5510718856806 +13.1686154693356,7.81021613751451,-45.609135167253 +13.1306634732187,7.58589806860723,-46.2937721989731 +13.2657965025884,7.46783283326352,-45.8657703125194 +13.1995845641512,7.56852609639783,-46.1723578003318 +13.3412562330709,7.47330347750457,-45.9263205893195 +13.4154576537688,7.41814055216932,-46.4198028039804 +13.4055188335288,7.49903792109908,-45.7767121277494 +13.492057432467,6.97468490395388,-47.015815253329 +13.4637928878391,7.05885816210244,-47.4407321668593 +13.518443611918,7.04612264723616,-45.5811946218734 +13.4257905357831,7.03814740509094,-48.5575847662481 +13.4080918131369,7.02001525043709,-48.7979979915696 +13.5463313029149,6.81242182756326,-49.7467384426303 +13.4644510721824,6.88838871868069,-49.673697499635 +13.2264837050595,7.02575545627813,-49.4534923245202 +13.5344801436078,6.65801686426783,-48.9437731697622 +13.623322337915,6.67631160799362,-49.5031170842493 +13.5553376424116,6.76126599207022,-46.4986808357107 +13.5345817800703,6.72317672307823,-45.0553219345046 +13.6917038595643,6.72280569069932,-48.5484018360073 +15.2199167259523,6.58763690898471,-48.9102764431561 +15.2858863535836,6.48085896878592,-47.862559871046 +15.2393290567092,7.06857106054021,-45.7229946805228 +15.2154280329145,7.19283097372488,-45.4789206709944 +15.1715260151668,7.10048381314016,-46.6226933596374 +15.2124518869415,7.16771093924779,-46.0060187857549 +16.289411318833,7.73824828453008,-49.8481378405923 +16.4849731304614,7.72483916821393,-48.9861489570606 +16.6444112575028,7.4612702243286,-47.200625532811 +16.7457687020885,7.41322039804365,-46.9646565753057 +16.7982314992851,7.3457112294604,-47.2174868253379 +16.7948128061375,7.42658884781029,-47.10198153748 +16.7836694011978,7.49752148054763,-47.4799855708832 +16.7680087297662,7.7167792241858,-48.5065429143185 +16.8662068596861,7.87402670407094,-48.955447055696 +17.0082957434198,7.90815164235203,-49.3853642744993 +17.1061695624588,7.98574873140119,-49.9671358217117 +17.1748107373874,8.02094042137001,-49.7734019175157 +16.9751217879748,6.5188454782126,-48.5677569390657 +17.0449948507573,6.69463213898369,-48.6123256303262 +17.3763970502342,7.66531531635388,-45.9830204983846 +17.432603540433,7.48268905287336,-47.1976348021576 +17.3180103087155,7.73105519002745,-45.4694137492636 +17.4116229187999,7.59637099715034,-46.180808910863 +17.2167362947233,7.63142275507162,-45.6474908311051 +17.0400160828526,7.68803611649679,-48.4723839404247 +16.9434241106977,7.67206728945493,-49.1981830985096 +17.1458685477373,7.62792157388032,-48.0071651672508 +16.8463643714006,7.72177049428435,-48.5689687693814 +16.7051294289593,7.71414708228437,-48.3579165546317 +14.9804206263743,6.84808547014656,-47.5886321116462 +14.9650644096172,6.66876857178911,-48.8745059955467 +15.0028601387054,6.48017832217348,-49.2579672633651 +15.0083382246343,7.29389589350917,-46.2041660968196 +14.9918316583455,7.17699498595785,-47.0464194409195 +15.2627932224506,7.10352593296746,-47.5375845515786 +15.0324757970631,7.1832331619187,-46.7174143739767 +15.1471611741271,7.16262125573764,-47.5430340713367 +15.3475130540169,7.18357537989509,-47.831756456359 +15.3923464086658,7.24466877683796,-48.7435635461678 +15.408706824017,7.47836505249067,-49.6904940298704 +16.0841463233505,7.14076630917315,-48.3458290137292 +16.4529509862433,6.69490024208447,-48.7828601134566 +16.3759767393764,6.88814547074631,-48.7703035760573 +16.5581739377234,6.65040146063645,-49.3959470527858 +16.2449636985946,6.98920457578942,-49.7891380929961 +17.028135279285,7.1979078702484,-49.6983385831942 +15.7654193808343,7.65068862486853,46.9436281630093 +15.8134656021336,7.53086863349397,48.7253592812148 +15.794609859455,7.57926683098646,47.4948490971957 +15.8487719503123,7.51968304884251,47.3479373794857 +15.7875844757294,7.73731060618328,45.8830071209779 +15.848209602579,7.49051420265515,46.9575181106055 +15.8606458284324,7.57184085609615,46.411812335823 +15.8234911860393,7.61793962131447,46.1991592543591 +14.9150488258455,8.35268940722463,45.1238079308298 +14.7406485075584,8.17988467021798,46.0289631541762 +14.8566323224254,8.20813853806118,45.8321161504597 +14.6564624732554,8.22059561767503,46.0522848330036 +15.7187959471256,8.07041618359471,46.9285900877383 +15.6328433807651,8.07545253637161,46.9494642335783 +15.7153422070554,8.07403684284688,46.2617677450036 +15.6986215613714,8.07487562590248,47.0361498707377 +15.7956977120433,8.19086136552177,45.8038578537538 +15.6946762196992,8.06963974469962,46.3013804906336 +15.8498866765761,8.28570334072257,46.2351611215446 +15.8502544488266,8.31150035257978,45.8766381627551 +15.7330097787157,8.22867543910503,46.7794275688766 +15.7822735809386,8.30723378491913,47.2755553310436 +15.8220945687365,8.34146140593775,47.1338933767569 +15.6679664840136,8.01944941302144,45.6586006372567 +14.6272705600327,7.5715112454634,47.8526095457071 +14.6016004060576,7.61309903094755,47.9652625756203 +14.5876418031444,7.6719792450305,45.8585695732641 +14.5990316854932,7.62928185264908,48.779739581698 +14.6172870927862,7.55809577143767,48.9307849757608 +14.6590791478023,7.35044677155959,49.1592473842882 +14.6203334717562,7.42993055722172,48.2966703342587 +14.6618267678755,7.28523532456509,49.5930345144331 +14.8215162057616,5.91526167931964,46.8511553671964 +13.5389309612272,6.81627923187655,-48.1961840589736 +13.5693315517078,6.74495394479447,-49.0220637419274 +13.518149531295,6.80388794848909,-46.9018065109523 +13.5072882567955,6.80218563297241,-45.9804851441675 +13.648030189117,6.71176074537224,-48.8533818446442 +12.7602131220817,7.43964909878157,-45.9165160741023 +12.6639477290028,7.59151070793876,-48.1340063029421 +12.6901319401189,7.52717489474331,-47.9455922695219 +12.6420786986126,7.67748859426301,-48.0963768587307 +12.5951340951274,7.63794308055262,-47.9689159717265 +12.5652200939924,7.07513482567504,-45.6822018117831 +12.528637918322,6.74731380820461,-46.0318197460979 +12.5170002886615,6.54284235014806,-45.7907648553442 +12.5249822790776,6.36200549131662,-45.7483387113669 +12.58131855009,6.32206077469042,-45.4106657596312 +12.670798181663,6.54271184641294,-48.6930171771956 +12.7667438599411,7.05346972069401,45.3002041859254 +12.7206512425362,7.75083426936586,48.6308204666803 +12.7164764854882,7.63119986263476,49.1201667917241 +12.9505225323584,7.54311952460527,49.5539899282658 +12.8052289686535,7.59570779912235,47.7600933091701 +12.8639643449766,7.5568716112035,49.256210259416 +13.0703606245904,7.55561597336079,49.5737606819698 +13.2300590308022,7.81520443284291,49.524957815213 +13.2628714132732,7.89044809601186,49.6531326133157 +13.2292188345501,7.76301927641309,49.6045031690221 +13.3121404128123,7.89425692078553,49.9177245249901 +13.3528148700715,7.67172755942626,48.2231014801559 +13.365879174232,7.57985371148501,46.8770834386094 +13.4208174741614,7.60153332065522,46.2785355435862 +13.3579530498787,7.69414696957678,48.9345253573323 +13.8371543021166,7.97546961798797,45.3879002320824 +13.5314194247422,8.58690442442809,46.1061731130529 +13.5404702499743,8.54041917542957,47.3492473612118 +13.571445917605,8.48469757675308,47.8642876010672 +13.634364702428,8.46743205381624,47.7349784508048 +13.786702457989,8.31524251442644,47.5557442012436 +13.7282178682302,8.40526964040271,48.3029594322012 +13.8177505682066,8.5182451233659,46.3899114130381 +13.8423972765516,8.56406932501867,45.832556750818 +13.8179576130901,8.62844328856771,45.0351307217506 +13.7684824900026,8.66002524755996,45.2225471293966 +13.7201656213524,8.62814895000856,45.2672131975625 +13.6900243666952,8.35003882528506,45.7422797731133 +13.4958635825719,8.63717256205202,45.1865994552153 +13.4942963294996,8.5308571807982,46.3034021375962 +13.5061167240263,8.43127613542862,46.6319664269624 +13.535251224772,8.08094533413698,46.6528751976681 +13.5080094860227,8.23799156582861,45.9217705803881 +13.4841366774618,8.35680845293888,46.1596207000798 +13.4216308523461,8.22757204445374,45.6291328676525 +13.4449305222463,8.30699644950272,45.870696439499 +13.4189577379442,8.02168016520805,45.4642902350013 +13.3943422578803,7.99507267166646,45.7923545258331 +13.342653561607,7.87569364355872,45.003274050704 +13.2273297045767,7.8800758903802,45.0965524613237 +13.206324631033,7.91198308971157,46.0647198900596 +13.1761074384742,7.98605141472438,47.73215568509 +13.1406948147769,8.11151617599506,48.9659083206909 +13.2239157630014,8.04227016891164,49.6722089837893 +13.1097325538862,7.54181355760118,49.335533728266 +13.1598450460397,7.58213203423168,49.7756787193556 +13.0943346840712,7.11577776289015,48.7973737985139 +13.0881927703501,7.05161151376681,47.7969299384482 +13.1036332063895,7.10464654474407,48.5492127435926 +13.1493680076954,7.1279345149669,48.4886704354493 +13.1475959645599,7.26784487293638,47.7030447744979 +13.4712866520625,7.30760578528996,-47.5048470329718 +13.4078246303106,7.11954067571666,-49.6495951533503 +13.2823157629772,7.05439013207242,-49.4035420290229 +13.2111672120563,7.12453071592397,-49.4431948744261 +13.2454420034696,7.1122050317142,-49.4398672938193 +13.1483621817753,7.07140453194917,-48.7176086749079 +13.0745348583665,7.04064333906996,-48.9585758036457 +12.8458337845823,7.09600274956465,-49.0726357156093 +12.9880055897594,6.999896495973,-48.8443757771483 +12.9581137672136,7.05236171210038,-48.7191604096697 +12.9202119648842,7.04277581215751,-48.712612734039 +13.2741325744229,7.0535617360664,-48.8657187429997 +13.3286500077,7.023562584197,-46.8557311629185 +13.272790840457,7.16149380305746,-49.9329355594442 +13.2782584873532,7.12949601368049,-47.9372289927487 +13.2898276901363,7.08848943746323,-47.7256109512707 +13.5076088014533,6.8459424081236,-49.8339064284439 +13.4647846343757,6.85758248399421,-49.4865693665458 +13.3997157338506,6.89578928408653,-49.3228659022672 +13.5645767997804,6.83028494081125,-49.657475650375 +13.5855378314819,6.91930951612331,-49.0328525000443 +13.6035553251316,6.89432708704056,-45.3183505539162 +14.1245419087298,7.21033170298148,-45.6756448569757 +14.5694729153201,7.21474260118734,-45.1407749073412 +14.5796811454411,6.98750135061408,-45.7923597522185 +14.5746945380992,6.86021786843393,-45.8850126844512 +14.5870744043378,6.96728644605323,-46.9975680881444 +14.475837808194,6.94238993687099,-49.8193384344739 +14.5229385080949,6.95799030373212,-48.6597922902766 +14.1395543857841,6.79920573039616,-49.6920093322933 +14.0779389036511,6.79652235231675,-49.5324676093556 +14.0638022784451,6.77072907451885,-49.3371164589797 +13.9812728059413,6.61215508682264,-48.6127376258114 +13.8993070179874,6.81586745051683,-47.6801609117763 +13.9387332986824,6.77670228921841,-47.7967621642819 +13.8655033951731,7.10438582511754,-47.129648773146 +13.8855606472287,6.95239177501762,-48.115695821997 +13.7947555176845,7.17907046435439,-45.259163855145 +13.8248924853716,7.16699740957022,-46.9285348987486 +13.8714567578543,7.16544720801521,-45.2394533297863 +14.7509095263541,6.82344111268535,-45.425707136227 +14.7361631369437,6.79589102518371,-45.8556440593194 +14.4228345403213,7.4094583076725,-45.2365937183849 +14.407674222609,7.36779224277449,-46.0241002978677 +14.3683026801435,7.31937485110401,-48.0746270812232 +14.3470236079568,7.29687845423708,-48.7321138394238 +14.2540725897207,7.84564962737658,-49.7087071246561 +14.2912624298975,7.69165727723212,-48.1233418870723 +14.3453821816124,7.58102739934611,-48.9449834362158 +14.3402249792101,7.71573989902609,-49.085531595473 +14.2231049606302,8.16147659080729,-49.5464576038335 +14.0295230073694,8.83958264434866,-49.9193277948164 +14.1104622491015,8.70633199245927,-49.833649243696 +14.0730222362799,8.7316279264273,-49.5792264666942 +14.0047251826924,8.97175650269787,-49.3436970374695 +13.9630451200187,9.03314312375327,-49.3198824458158 +13.5942361511614,8.20405326578591,-49.0263694845706 +13.6120762851111,8.25015752120804,-48.4588772495539 +13.584352088037,8.23806624649058,-48.516329581321 +13.6808271543284,8.36087414898756,-48.5275040579756 +13.701579910988,8.46969968860561,-49.1226241351502 +13.6999405129552,8.97800373071534,-48.3952485589813 +13.6464638567822,9.06443226337032,-48.0677732414132 +13.575499385604,9.14880988796859,-48.2483126574769 +13.5081082229284,9.2261043854262,-47.3924811390071 +13.4139193762437,9.15292975950751,-47.108322340861 +13.3450572738085,8.88675190994346,-47.8005781757105 +13.3173693906755,8.61780060507114,-48.4013906563053 +13.3113645776846,8.7308244002876,-48.362453058706 +13.3200589650217,8.42223460035066,-48.2473682614123 +13.303441385021,8.27064628339834,-48.4033028343758 +13.2776082708889,8.55115958946191,-47.1560856022769 +13.2470552952564,8.78229179670852,-46.5804723205187 +13.2831147900098,8.72796696040153,-46.8106192894882 +13.2015623291294,8.87280562266251,-46.3992378792157 +13.1922362094761,8.89427464914849,-46.057288564226 +13.1848602290241,8.53589144121243,-45.5692221864209 +13.2003064527202,8.43477552192133,-46.577827862137 +13.261695619414,8.35089422606759,-46.9214834035399 +13.4057119801672,8.4301732532623,-48.6902725087908 +13.3347593760464,8.31139486587136,-47.3394994559027 +13.7089438296599,7.98486509685881,-49.5351205756729 +13.4728657046123,8.06419134654796,-49.5526707761847 +13.4123632813643,8.1062911201985,-49.7341264525469 +12.7178304944093,8.18144702081816,-47.1773407653585 +12.5958031567723,8.11325486242144,-45.013987942956 +12.4932231394175,8.0107943071475,-48.2862417868494 +12.3853431178222,8.36135475387456,-47.0554762052981 +12.3672763361211,8.42701968327625,-47.1730192894118 +12.3436303607565,8.44847009352241,-46.3021730548376 +12.2923988798808,8.50739677439526,-46.1647293146668 +12.2331856328225,8.41605093805093,-46.159552863621 +12.1860900329318,8.31757256852188,-47.4531939602884 +12.0962272598814,8.26789983696738,-48.5491596181664 +11.9934963283707,8.29002684330483,-49.1428019987361 +12.1463590776473,8.30934892839684,-48.4404490750192 +12.040171241421,8.32203723306064,-48.7575166055253 +12.5795900471083,8.27516490673442,-48.956152960025 +12.7012503590509,8.55214436445607,-47.8873811715154 +12.6326118835087,8.36427285398684,-47.8584965918829 +12.6744105846328,8.50397262005159,-47.732620381904 +12.7663062061346,8.40798440473908,-48.3192263163247 +12.8526525090105,8.17930452237865,-48.8507948252709 +12.7066291691201,8.51072039404204,-47.9745130375078 +12.9402165632941,8.02905853349105,-49.0357870485995 +13.1487263097084,8.03710148233021,-46.2496360523964 +13.0400123591578,7.97840798897467,-47.5400037891786 +13.2676010580244,8.19059058733816,-45.7050760555661 +13.3537951194953,8.41705571410397,-46.062377207333 +13.4360904981619,8.67363870840514,-49.5330644706991 +13.4089172533579,8.57661270281134,-47.2364119012849 +13.4555351186814,8.08765384451602,-47.7988366610305 +13.453722911947,7.82402444252277,-49.0309226239626 +13.2048107704325,8.31939657018541,-45.2902570710233 +13.1537221278631,8.25487243493143,-45.8555926157253 +13.109602953903,8.33222358542999,-47.897315064137 +13.0932080733602,8.40647857895181,-47.4646835839008 +13.1083394541123,8.43174158375544,-47.7819288632742 +13.044019461976,8.37718553149977,-49.8696762034784 +13.0615831772709,8.96124042032916,-49.1133327116329 +13.122358641338,8.7680874441805,-48.2532481983876 +13.1242974398623,8.70399401107234,-48.3417132246562 +13.1285517912243,8.61303043068281,-48.4453516431008 +13.1378152364392,8.94114077750874,-48.5987826476315 +13.1153530078056,9.00501054748223,-48.7605809586277 +13.108149195281,8.39615026900257,-48.186713776634 +13.1115688803233,8.22480429300916,-47.3969118576667 +13.1521286521078,8.03639395661028,-46.8686636251153 +13.1889340070599,7.92915076246638,-47.2195115411771 +13.2137873667293,7.96740233745787,-46.5664302855239 +13.2503779100796,8.17606934761022,-48.0354372633711 +13.2907323049301,8.21818763121155,-48.6463606409425 +13.371097383612,8.25025819537782,-49.4142634795551 +13.4733380421712,8.30935142821569,-49.4480863969841 +13.5920034940228,8.23954252382059,-49.5947693480845 +13.9189445317401,8.32967136276029,-45.8402491135377 +13.7946842945917,8.3889843844872,-45.591194144856 +14.0357782940644,8.40475442942828,-45.7158212320639 +14.1280501329482,8.4050918661662,-45.2904803983297 +14.2233011635593,8.33759131954929,-46.0909774601779 +16.2270450133236,8.36542640687926,-46.2490347360896 +16.0263457487572,8.33454124913614,-46.2403655544539 +17.7401734857882,9.34896851767549,-45.5509795048557 +17.9069870643393,9.93576819224852,-47.5928147458331 +17.8268784949667,9.72600149992155,-46.5930785117009 +17.9801222334653,9.97280776639236,-48.0340256770167 +18.0915969325515,10.0418424423492,-48.760128692479 +18.2459667198119,10.2612123259597,-49.3782098285967 +18.4569838079084,10.7002266526707,-49.3658226337064 +18.4217337714747,10.7763540914725,-49.518508217959 +18.356825464588,10.5755948977796,-49.5312154466169 +17.9254554881667,10.0060275188019,-49.6634571810862 +17.7845094126968,10.3171523821669,-47.8562351823627 +17.6903337547543,10.5546171693446,-48.9656396749051 +17.5859705657568,10.6233431326177,-49.5989254403403 +17.8381734245508,10.1451167577492,-47.9754787495436 +16.6653863915391,10.2297185685964,-48.2812961116577 +16.7721663385305,10.3180775717201,-49.1202070529908 +16.5576528403192,10.1667113582971,-47.7347459621251 +16.3825572871189,10.2550439434403,-46.264542515116 +16.2610229510805,10.1773984828114,-47.9790120781593 +14.5685111877596,9.63643859389644,-49.6763586373929 +14.5283646608182,9.55797144287351,-49.7850335241665 +14.4626855533955,9.59372750317575,-49.6306728544661 +14.3341353781018,9.7271259653006,-49.5149286453813 +14.4090323047362,9.75196481735702,-49.3610010959034 +14.233154140923,9.83599806381622,-48.4299507145377 +14.2807012479911,9.78458375929391,-48.9097871989443 +13.989154322204,7.78194306332007,-46.1914566719846 +13.7569186052986,7.57598958508072,-47.4164403677688 +14.9507199755902,7.4531837513804,-45.8880460347661 +14.9486582670115,7.43236526826786,-46.9864627611686 +14.9273997031245,7.40959420042031,-47.0106844808849 +14.8758055037603,7.43556274873168,-47.021874910157 +14.8285409396123,7.40039601690909,-46.830022939691 +14.8179510715706,7.30464687421168,-45.7078487439456 +14.933151420978,7.41161054044012,-46.1162046465424 +14.8521995285657,7.43785875615059,-46.763095205544 +14.1365293214623,6.79305660270076,-46.0294068889455 +14.001307602549,7.67714282317485,-45.0634449415398 +15.036638915997,7.53527089551789,-45.7084818905208 +15.1106819153139,7.40958166767024,-46.072415521314 +15.3063597323057,7.28507567006078,-45.330901441949 +15.1482028805309,7.36231784431108,-47.5963409324014 +15.2210668972582,7.21515655231703,-47.135904037978 +15.0462352558338,7.24623031319794,-46.36487003666 +15.0871061346363,7.32661570047533,-46.9173329003055 +15.0986503638987,7.20449034433236,-47.1680757309171 +15.0610891419139,7.22909077301482,-47.3417958773143 +14.0804244347751,7.55199881149498,-49.4660317709271 +14.0444832247476,7.74682840708042,-49.9962223366322 +14.0999149560323,7.41705543411667,-49.8947070386727 +14.1020490907474,7.27614923528032,-49.1164528098377 +13.8808071054457,7.7821494602881,-48.5759620324955 +14.0218021475083,7.77991666390221,-48.4206361382161 +13.7367928053082,7.59121402704663,-48.2753718024552 +13.4583208652001,7.20614723634916,-45.2940456555589 +14.6914689203247,7.41603550129895,-45.3526756031183 +13.3669483052888,7.04728120435468,-45.3442064462853 +13.2776211208514,7.07299102880503,-45.3331434714975 +13.2101051506798,7.08913025437785,-46.0322542523962 +13.1930931898071,7.23857298769776,-46.038269860617 +13.2298852646841,7.02729257450719,-46.0227474677239 +13.1935173301278,7.29727006290242,-46.8759703001637 +13.189202364161,7.39901051683985,-46.9384888026723 +13.2599585168435,7.68461669941006,-45.229646602703 +13.2314085374641,7.56350230160343,-46.5965877320265 +13.2627745448076,8.08923677617967,-45.0265836363445 +13.2475458269377,8.32287046659105,-45.0691036844603 +13.3016604659669,8.63604530303235,-46.0256374571637 +13.3775336501997,8.62177824157155,-47.5548127273155 +13.4949273847867,8.53371101997822,-49.1174931395078 +14.7342320421506,7.39859287767579,-46.0053444179646 +14.7787419965196,7.55857637400738,-46.9762856623865 +14.8455336643925,7.66792158039619,-47.5354190047713 +14.9020285297995,7.57127738657614,-46.5209459818777 +14.9446235095596,7.38697702679671,-45.1460536379863 +15.0750895371271,7.19532149077297,-47.176183989432 +15.0881905968877,7.17805056846971,-48.4574721415402 +15.0891353604929,7.09675634289623,-45.4804163513932 +15.0889726094092,7.29314799704779,-49.0349070290524 +15.0844160053402,7.21385451773578,-48.6485039424807 +15.209973235559,7.93979212774932,-45.4916893359353 +15.1208124676942,7.75865823346702,-48.3866710133398 +15.0909807560124,7.50481332410083,-49.0084230855618 +15.1629923788735,7.89534449255885,-47.706960818233 +15.2006799174381,6.97611338881066,-47.1032589568183 +15.2061225904442,7.15846073105989,-49.4562111011456 +15.1996014322633,7.09456908977534,-48.5382165854456 +15.2149200290362,7.22547258826531,-49.8700229439809 +15.2894990676684,6.87314302068229,-49.6273160199317 +15.2845025226738,6.84017597692211,-47.6401326800114 +15.2230887741096,7.00850825895042,-47.0593207869938 +15.1449048585095,7.02995177139602,-46.7739603546116 +14.9874427837408,7.2390056676359,-45.6074578267736 +14.9499554469616,7.39056204343347,-45.7030943862707 +14.9218607906534,7.36999917153958,-45.6821927921536 +15.0479111586822,7.14092468010813,-45.4828513200987 +11.7293263628628,7.18050460944474,49.4916318992581 +11.8796892548967,7.13050837016822,48.9832940389661 +15.0144731006357,7.28516549018673,48.0565789548345 +15.1901663967839,7.54514772774603,49.7450218973476 +15.0655040448932,7.46560958203084,49.4242279884173 +15.268469980742,7.23116300256844,48.9164199621772 +15.1246831553206,7.57635394131072,49.8526595522677 +11.6024991614411,7.21812678793738,47.2574324237921 +11.3676441069819,7.24771763475252,47.621386471123 +11.4983320917998,7.24024050969744,47.5287064947782 +11.2676098204112,7.27513527966355,46.5857431012948 +15.2543595116397,7.45260702647461,48.9322385903004 +11.1699252284706,7.19873859927652,46.790715755025 +11.0121251617371,7.25377221962661,45.7201718440494 +11.0887638607337,7.16570159904045,45.5201585851322 +10.5649210071255,7.80685355686199,46.2149570981648 +10.654260031474,7.69598369017351,46.4779899718892 +15.2744324619879,6.88317959914481,48.2890691744916 +15.2770850261741,7.09113395674962,48.8732227303067 +15.2742962892493,6.67720563254344,49.6044982228463 +10.8252334841205,7.54288186418449,46.8972795155747 +10.9373733581499,7.53637519799442,46.9049336865333 +11.0406169472861,7.55433621841777,47.5711913254671 +11.131706218462,7.56761517144064,49.5862490150218 +10.7407474516915,7.62416232696893,47.0950499098075 +11.2513750119006,7.68550426187443,49.1190108235717 +11.8381723194882,7.9031781760274,49.4174211443766 +13.185763564292,7.90165179281363,47.6446595464144 +13.4151323975402,7.89342890342564,45.7420338811566 +13.0391044415877,7.72657167346789,45.677127912171 +12.8503267073107,7.67373670564313,46.3782344972748 +12.8569757236101,7.67491129230325,46.4261816607594 +12.7588771338913,7.64599244739323,49.8175889300887 +12.8734400846052,7.6472729971443,45.3308471544398 +12.794457652028,7.61046493339248,49.109186373769 +12.7959727881252,7.64877293629666,48.7941221309829 +12.7844246690031,7.55402802288905,47.007190747608 +12.7650690681741,7.54032416678457,45.1259476124164 +15.440135333565,6.97588543239796,49.8268039444461 +15.7657981504991,8.01399367969209,49.6440933051523 +15.8307085751237,7.99751403911179,49.4260386164887 +15.8868104339203,8.14732875134633,48.842181758303 +15.9419596361787,8.37593979955491,49.6243192525416 +12.4441620808057,7.20540090855161,45.6987458708027 +12.3623074775114,7.12223851367798,46.7206957479805 +12.2444497715636,6.95528471549751,48.6339917998704 +12.2159469167315,6.8434019615915,49.6196052342181 +12.2964053239212,7.03249801628882,47.4768819056457 +12.1032418104771,6.64789080570225,48.181138385502 +12.0913534023213,6.74334884126594,47.6110694251731 +12.1240475240403,6.80910529264545,46.8003398898002 +12.1380115527687,6.71738478835716,49.7707848754447 +12.1047712399993,6.84197406510845,46.7107947812128 +12.0529831583556,6.94523567619145,46.3832732027194 +12.0293704261126,7.20416885898653,48.3327464124234 +12.0439741390775,7.22541896579207,47.9492435487001 +12.0333065493788,7.0647977036873,46.713438929742 +12.1291322385317,7.21653858938073,48.8028686363685 +12.1444653373132,7.14872295084329,48.6191315334434 +12.1650272929396,7.05492959423884,49.890079477838 +12.0840781815475,7.26139690711336,48.5068828846104 +12.2008061886587,6.75826237182894,48.3542587120602 +12.2012906692631,6.6938838136297,47.7260665434625 +12.1782426456145,6.65701741550359,45.8906071740663 +16.0717642311255,8.33795592638537,49.9755712353049 +16.0960959161826,7.73564400199216,48.879459606437 +16.1108539097083,7.79246611660279,48.3241811526857 +16.1206473958603,7.91050383183653,48.4337071549892 +16.1233520327075,7.77519116384746,48.2049912765325 +16.1311264302331,7.94600405681488,48.3050112452674 +16.1637469729381,7.98770378858613,48.0491013212286 +12.1717602860889,6.57011673040992,45.0608444820484 +12.2614644418039,6.62807798938537,46.37272335342 +12.3607100425131,6.8096021844182,47.2135782365706 +12.4211617175761,6.92401742715944,48.9108144516671 +12.4658961641797,6.99688464145049,49.9331986565616 +12.4858709218087,7.05136275495476,49.5848485814768 +12.4810236125549,7.07080515610518,49.5052463978727 +12.4832838622046,7.02500907437429,49.7005720726018 +12.3681880187472,6.019563956945,49.8449324850758 +12.364124547101,5.95469711954971,49.9494909431934 +12.749746873632,6.57129890075874,49.3141414631469 +12.7975178528724,6.4953672341714,47.6569641908039 +12.8261703338482,6.43590062610302,45.9871287115393 +13.0338101781271,6.15454499727318,45.7564990882126 +13.1870407572168,5.99237455714434,49.2562408987435 +13.1138354470951,6.04893435494483,47.1197418375776 +13.396949623684,6.11580349753162,49.8083600819877 +13.3757868394562,6.14013962050246,49.8539749695993 +13.3375084418709,6.18749762279947,49.6265862781965 +13.7499308096639,6.59346590900655,49.068055252148 +13.735927759825,6.65673771789321,49.6760000873273 +13.7119074843229,6.53659552495984,49.5904613670553 +13.8440384609809,6.68763680006708,45.6208179079321 +13.023912837251,7.00192101716661,48.378907675965 +13.3124766776211,6.85972360712108,49.0781401819838 +12.9376963317138,7.15973992864784,47.6785778027347 +12.8627639988786,7.23106187603711,46.5774691014806 +12.7831185862055,7.27183511483679,46.6720073854187 +12.512972282617,7.31118930233707,48.363647771866 +12.4181241166145,7.40259388902832,48.4195889258851 +15.2288097627401,7.45209094482806,-48.5413379822302 +15.2154618632882,7.6536857691803,-49.772096664007 +12.6131418346041,7.31241809950429,47.0296836575137 +12.6952070687553,7.33125804978354,47.1466159075622 +15.1098054636915,8.31780493801985,-49.6002036177731 +12.2394755056941,7.49189895848995,47.5193007671452 +12.1966066192618,7.48406322191599,47.3850076125106 +12.2596513204195,7.44816490761708,48.1793904834464 +12.3313064942807,7.41931220092599,48.3253867793486 +12.1286971672461,7.44496965487867,47.8200310721537 +12.0310305326779,7.38768300731717,46.6531170518676 +11.5892163706557,7.15822680940837,45.5367256907397 +11.7331083119065,7.22670428477166,46.0103713922683 +11.877489328438,7.24552339323513,45.7233001481767 +11.4607825812598,7.10805917850365,45.1866927980981 +11.1379929702314,7.13313035989616,46.312278217013 +11.2290169004864,7.10053704409377,45.5580780541208 +11.0501893922127,7.14291308553897,46.8944855101927 +10.9869332872724,7.14223567137809,47.6008327938761 +10.9470174639801,7.13013633848708,49.2147609420031 +15.094224830522,8.08619888072348,-46.6066081791475 +15.0927890724332,8.15997512764157,-47.4309193610349 +15.0887583212444,7.84801482435011,-45.1023000420141 +14.7917637122124,7.39337261872271,-45.1527423623531 +14.7481582149378,7.46452991676195,-46.6214406263225 +14.7312825212838,7.40143917518236,-47.2890640204036 +14.6969126085827,7.4002321405516,-47.3106818445025 +14.6781706418547,7.41829174648934,-47.3168546440472 +14.6541678166777,7.24519163091277,-47.2848652486249 +14.6403925988828,7.18434872444047,-47.426639780007 +14.6574833558185,7.09676532781744,-45.717391728794 +14.6047960251326,7.06921169636458,-46.3466350673681 +14.6412408649178,7.10544568292062,-45.7118309114728 +14.6636043312973,7.2321304282852,-45.4852097923946 +14.7137960858216,7.04605765024533,-46.5557699535195 +14.6213246077914,7.03917431387645,-46.5560034739137 +14.9428024294196,7.12579782312446,-47.9285046314668 +15.0334469685465,7.07468086589633,-47.5642838300137 +15.0823399688509,7.11568133110061,-47.3438182000018 +14.8118925448915,7.15358968608873,-47.18587531012 +15.0731821514244,7.14676874647392,-47.8095307218497 +14.6722128010712,7.77234477785776,-45.8555221798378 +14.639373657671,7.71009737895822,-45.661159218458 +14.6956401635814,7.82660300048005,-46.2716812016915 +15.0926187795666,7.20940217212419,-47.0963432158343 +15.0370102000298,7.29459338693367,-46.5815393960164 +15.0800911371553,7.26507762989887,-47.2249305637749 +15.0291585897567,7.39932581574668,-46.7684776437836 +15.0024620202308,7.43157653133461,-46.9486377482172 +15.1361144696663,7.33837788820408,-46.467190109791 +15.0879602614795,7.34427223770478,-46.7610669814252 +15.1897579027189,7.32717002773492,-46.3848438652548 +15.0603271552008,7.35231375619735,-46.9099867327799 +15.2256954688985,7.30714526475834,-46.4759122641478 +15.2309012404808,7.3127224343858,-47.2012622335143 +15.1171691586594,7.13419413391693,-49.3480302805595 +15.2103335681739,7.19106697564663,-47.8823059277258 +14.8095257042877,7.2442696560125,-45.9866431176511 +14.7768662438358,7.01566368005315,-47.4120376027206 +14.8091438121052,6.57005712690532,-49.4737691459577 +14.6882526620837,6.49408893951011,-48.5838712163492 +14.6792771715651,6.44719015630113,-49.2584041096921 +14.759466948088,6.47794645803741,-49.7376966160821 +14.8779900241771,6.54154188386397,-48.5880336977601 +14.3090472213851,6.4728214467034,-49.9931978798942 +13.8044890540849,6.57548241484977,-48.0041315703528 +14.7822309079255,6.79235150226063,-46.7580778529501 +14.1102373255258,6.49737456477205,-49.1487586118374 +14.6805607008569,6.67970263665338,-47.5318631309248 +14.8617255276682,6.82869133632421,-46.2623756971855 +14.9458200374938,6.85018446387285,-47.6298358675723 +15.0019892033923,6.79540885877577,-49.0340530348563 +15.2428660821746,6.83931461364946,-48.2398343693528 +15.2476751356017,7.00379691095569,-47.4054298900357 +14.7361413647081,6.90683894022074,-48.0841959920172 +14.659884281566,6.93039152004735,-48.4924512225623 +14.6377273102162,7.08751900523478,-49.5195429423718 +14.633555350386,7.02923743670914,-49.5532344903737 +14.6186733712476,7.0445263679875,-49.6952981140421 +14.5797787397419,6.95561456296582,-49.2936897866192 +14.5018780320103,6.97428323148236,-49.6420692382481 +14.452935447084,6.94525776607721,-49.7092311942145 +14.7003479451848,6.78338063539471,-48.2016635043153 +14.4165262343705,7.08564776754279,-49.282529475478 +14.3774797992849,7.20224738893278,-49.9504627944201 +14.4266732318857,6.96583758014546,-49.1855381595269 +15.2172643283206,7.12613767586944,-46.7778399981451 +15.2032980841445,7.2236136588275,-47.4960290690198 +15.2227747464402,7.25679770757886,-49.0787313163821 +15.2455755546784,7.07333176604349,-46.4239032185456 +15.2302655279034,7.74599821794287,-49.6934080712286 +15.1897236627896,7.66396724516546,-49.6020158179941 +15.1760966663401,7.62109126809897,-48.8484989000595 +15.1479774823717,7.45279389347076,-48.7847511054993 +15.1320726428179,7.24422614883849,-49.6708738999766 +15.1025411991726,6.85792462940558,-49.9049049110327 +15.0770064500623,6.83350669627085,-49.3568417152236 +15.0493498826668,6.75026547246783,-49.1680018320929 +15.0153965266556,6.66444753357598,-48.6655874042381 +15.008002339418,6.53131755396728,-49.5839218827942 +15.0273786172388,6.54703171067822,-49.1477732826144 +15.0103576592057,6.54621461142536,-49.1956291443607 +15.0133635757501,6.65275295922276,-49.4872736697572 +14.6619372895209,6.96524675670328,-49.3634703258929 +14.6140256094828,6.85504249754114,-48.3336332381775 +14.6328312125322,6.93428852823025,-49.3894678470679 +14.3247050288257,7.08769272046032,-49.7019235515595 +14.3181463479566,7.06046655422555,-48.948778677529 +14.3101830467565,7.00860237468431,-47.9009955417407 +14.2343290050358,7.23764086658587,-48.8303398721288 +14.1890817798782,7.42813649367504,-49.4623390559411 +14.6056901233614,6.75927594367757,-48.43848661849 +14.6323685392237,6.62542141053285,-48.4808660321951 +14.6634004369557,6.57711786042054,-47.9625903074752 +14.6822253236247,6.53256297850199,-47.5741599994808 +14.6647907058529,6.63148801166511,-46.8879281069828 +14.2897988059972,7.08259205877637,-48.3384086954622 +14.6465222391392,6.6620140691533,-46.1712995882911 +14.5917670708063,6.87620069665569,-45.7294935567397 +14.6243193137614,6.80278589836744,-45.8311435813793 +14.60950109904,6.88487167593984,-45.8961622728227 +14.6209412948586,7.03867902114168,-46.382101211435 +14.5933522593647,6.95363578627466,-46.5230762381659 +14.5455736393994,6.90505734168364,-46.4194249735652 +14.4883750864435,6.88361544540549,-46.1830203556197 +13.8892018994575,6.87458395286738,-45.4402132972512 +13.1223772782442,6.75562099435062,-45.0652890613193 +13.5063197606455,6.59686626000147,-45.7557532312183 +13.7563872817879,6.51181752437447,-45.5338605568993 +13.2736517987708,6.6353822462161,-45.1152405818557 +13.9856290519828,6.4659337840842,-45.1889371717693 +14.4767531339533,7.81189063730412,-48.4124865358412 +14.4698586686166,7.8154062603401,-46.9869914241254 +14.4632641129593,7.84207316538076,-45.6017682086145 +14.4398667739904,7.74003059783215,-45.3381547024158 +14.4526607420151,7.73656265818188,-45.3955230495445 +14.4765404796936,7.62246250314682,-45.7828378617135 +14.5283703680421,7.4097379194971,-45.4435396654181 +14.5805703326304,7.33034268509219,-45.3007648618474 +14.625254619658,7.28734072201773,-45.5023504398089 +14.6292859260881,7.19807312153816,-45.135729982554 +14.6068447902478,7.34284891803291,-46.0715214320749 +14.5594617009351,7.45250625406176,-48.4826819946219 +14.0152386771427,8.25363488729433,-49.6161237694008 +12.8031565386154,7.04087061753368,-45.3534138177178 +12.5401095122157,7.01388572983746,-47.5809380444345 +12.4771590344192,6.97292571875031,-48.4439623600075 +12.6626035778664,7.0303294547065,-47.2504070776454 +12.4504593501783,6.86776618503391,-49.419167660289 +13.6791859724939,7.57857768266392,49.2183767429339 +13.6941917477931,7.49105891652139,45.807545036799 +13.7353559219897,7.56315432373914,45.4151063794762 +13.7863984257927,7.63716048818617,45.41637959422 +13.8275602312628,7.63761708112435,46.4008335883559 +13.9016072998369,7.53407720117045,47.5035445191384 +13.6839911502245,7.51688393396185,47.1476311794066 +13.97522376254,7.43609200442511,48.227019049005 +14.0717858877949,7.26820545179131,47.6526685737228 +14.1844370127789,7.28956957756451,48.2199594334799 +14.2646521651765,7.29413693230278,47.3587098559492 +14.3386352421835,7.33561195472432,45.0864304466623 +14.6183375669901,7.7621439907652,46.3605049222726 +14.728323314817,7.64748103025775,49.7767531548158 +14.7666992160612,7.41995469875339,49.8398130796732 +14.6814751471087,7.74834519789948,47.4300733195509 +14.7299273926559,6.79933296419856,45.5032288001045 +14.7514011124026,7.11307056942444,47.5505399857724 +15.9902499603105,7.8586001720855,46.332347490053 +15.8098255210764,7.75977179589753,46.6079364563469 +15.6211085373997,7.67892460697833,46.3119855313691 +14.94264527113,7.77685193536814,45.5296140523117 +14.6422639145022,7.7541242231425,46.6723986617002 +14.4314391075258,6.86038050367184,46.6575010822365 +14.4380691472522,6.81857880544894,49.057768964648 +14.428652818506,6.76759950575232,49.8714217654021 +14.3893394418086,6.6991941323696,49.6745033552234 +14.3896385729695,6.66543222178069,48.6533588384133 +14.3856379931373,6.6432521848714,46.316939603855 +11.7802057098631,7.45621469579453,-45.2249218489478 +11.8414940377338,7.43751198291225,-45.7888999690601 +11.9010083261523,7.39691376635157,-45.9467542581771 +12.064885484464,7.40675894171133,-46.9559961942148 +12.2036121666518,7.4865315096724,-48.5035421100271 +11.9968368516058,7.42690910777149,-46.1333818060432 +14.129862935562,6.99852375034701,46.3988598239613 +14.1086835558121,6.88061469822054,45.7288069692486 +14.0786244383512,6.80205828658895,45.0525068810588 +14.0512871086703,7.53529812914843,45.5081798334878 +14.027422862927,7.95860838321873,48.9537519721861 +13.7522535839332,6.64492338141612,47.6434898698541 +13.753124731342,6.73629683029572,45.5233192678194 +13.7559726978976,6.80099191006789,46.1402582858337 +13.7296392132491,6.95601554140343,47.5713461558988 +13.8036671403723,6.57715732010087,49.1649114207617 +13.7294082996668,6.65582957732946,46.4049878176118 +13.3505110200322,7.52280130856926,-49.9020952522841 +13.3096571905205,7.40670302109973,-48.6995959377307 +13.2632612227178,7.38619327099645,-47.3854496221581 +13.2474681246481,7.48812064986985,-48.1821179217596 +13.2503636987616,7.79712788638128,-48.6764159877326 +13.2496279128764,7.56036166266733,-48.5679167951936 +13.2828208172388,7.94680998756545,-49.1580655797408 +13.4419850438902,8.26891438277926,-48.5090219213696 +13.368657840907,8.12973096973549,-48.9681932510775 +13.5290178256716,8.26793747445658,-48.6109185115226 +13.6338195295251,8.15294866707327,-49.2716207383456 +13.7675748473117,8.00206344486237,-49.4105345060088 +13.7101421604932,6.90900120152703,46.6311666596064 +13.5018456551075,7.14472921450247,46.1682075641745 +13.5016298209499,7.25653608733669,47.0344812665225 +13.5347552578639,7.23054577082978,46.0154218505791 +15.3303613081025,7.93340660210848,-45.9528800638308 +15.3164371103044,8.05874981688514,-47.1241065027896 +15.2956831923118,8.12823200822302,-49.9758122507411 +15.2335143818568,7.93053058647895,-46.7907793995867 +15.2499211471939,7.93727124405986,-45.5354014263652 +15.1874103864241,7.84197881462028,-47.3012472625968 +15.176711670982,7.73480307696214,-48.3852435574861 +15.1852032290953,7.63375657601802,-48.3356717781786 +15.1541483582986,7.57617207572045,-46.4930308503394 +15.1649927927414,7.52649397595649,-46.4833694817209 +15.1794411512276,7.55311417206029,-47.1750006813031 +15.1871304181492,7.5117334420507,-46.4909409221341 +13.4611744374082,6.64134343850962,46.4231276569224 +13.4526504188863,6.52103536342205,49.1934121467532 +13.5360368338334,6.55090042840359,46.5501664251978 +15.2254763659807,7.54957968669351,-46.8367664601267 +15.1708998630288,7.50199478444188,-47.7557208301528 +15.0483610639897,7.44597107667782,-49.8126510686581 +14.9087468264956,7.28294421036378,-49.6574841494283 +14.7241543590552,7.22763981129518,-48.8968226338826 +14.5010231538373,7.20724564427974,-47.6211550578491 +14.1086572643624,7.17934116049891,-46.8070943846417 +14.2952725403367,7.14569011741115,-47.372012782318 +13.9666435871527,7.18481239880856,-46.0212995722198 +13.8948474312965,7.30434639242944,-46.2363951396182 +13.8272712221456,7.40206735994224,-46.5355392911603 +13.7151717608471,7.53408709402361,-47.4628431797171 +13.57920725296,7.65924244371265,-47.02707018817 +13.536068527612,7.69686517821025,-46.2492912217683 +13.6263312116241,7.60135517202316,-46.7673332845807 +13.4934449062924,7.69748069939972,-45.8527894822199 +13.4127283046066,7.69336780520545,-45.2621676651373 +15.3526934407234,7.80916473243238,-45.0150398233158 +15.4547087367846,8.08968070355037,-45.6820970756492 +15.3981320117768,7.9463970244807,-45.9690300985655 +15.5359041531503,8.22267657818057,-45.3573063411123 +15.6325301133924,8.43299926439558,-45.8003609216088 +15.7482738769341,8.75049863050305,-46.1297499280297 +15.8910890745691,8.90337976716033,-46.5877826566787 +15.9996651688579,9.04805532776264,-48.386719892369 +15.9534827705904,8.97278587111955,-48.4031087775165 +16.0011247460841,8.92101668415914,-47.3648799717526 +16.0117212613539,8.7888641240261,-47.3735733572308 +15.9595669484161,8.51228069063895,-46.5617062707472 +16.0010347940124,8.58621691544773,-46.4020374287424 +15.9547171038581,8.398185483577,-45.8343371690711 +15.9353288562528,8.42163987066206,-45.9159481830442 +13.6013223148726,7.23923796248215,-49.6946676485323 +13.5420927746896,6.88650761511738,-46.413717616735 +13.5670890449632,6.87699702342462,-48.8588855505582 +12.7640836218625,6.66223345203378,-45.3557137640499 +12.7560364441515,6.70174701166717,-47.5349106908465 +12.7678832851077,6.73460935555224,-47.2324023168801 +12.7067144593502,6.69507352956076,-47.7993211106058 +12.7387863831923,6.70341363682554,-46.8435786213266 +12.6808244815463,6.6170642882233,-49.3639995210202 +12.7777524527989,7.2219242830047,-48.8095651902363 +12.8106954194618,7.45899128787192,-45.6115807462469 +12.8183547518492,7.4445296496655,-45.1941650298148 +12.8511289215865,7.4060704496598,-45.2379224361191 +12.8649474300027,7.49522797685902,-46.8513321954498 +12.8864875045569,7.41544564206793,-47.9314670563998 +12.8999041315636,7.3125647663774,-49.7133384115138 +12.8824105754195,7.08048893224496,-49.9658356377461 +12.8716158614559,6.89074077688094,-48.8468805840552 +12.8855648647643,6.72213894150222,-48.2403194093738 +12.7851670550587,7.50819429716591,-47.4762813527063 +13.7664314140168,5.86841644208489,-49.6602691028301 +13.8519128573481,5.78669388195574,-49.7927980086257 +13.9855758062989,5.78257268993634,-45.5414829069053 +13.9396262432028,5.7775114195439,-47.7733812182519 +14.7454815127508,6.99214454971043,-45.053245734342 +14.8094121835945,7.11604492246923,-45.9052213960449 +14.6877367918753,7.0687910038144,-46.5695841584494 +12.884698157145,6.84092270163089,-48.1110542108307 +12.8923660755495,6.90947416822951,-47.4856854004314 +12.8483595765244,7.07979660783452,-48.6863701176596 +14.5798924976275,6.92587699276023,-48.2650367853914 +14.6175965654921,6.79772198936934,-48.9185172992567 +14.6532210739504,6.68564295811214,-49.20583642988 +14.5420827671247,7.03661719483603,-48.1800072999265 +15.0035016444844,6.22455579066679,47.3245372347656 +15.0592602659769,6.31794131247048,46.6923323125597 +15.1631823387249,6.38435230794903,46.0601292367739 +15.2044077467309,6.32039470276331,45.9115298085209 +15.0885240567083,6.40306218200639,46.3185653360122 +15.2383481015712,6.18036418673299,46.6790870595677 +15.2474631601949,5.82080331232974,47.7338350273017 +15.2653959631151,6.00928707848371,47.0466522185027 +15.1408347681822,5.59832327416521,47.8428644956883 +15.1824516542274,5.66612309137349,47.9874159545382 +15.1716532627084,5.68758080391353,47.1231967018039 +15.2982127806693,5.8885449211283,46.1876645824572 +15.5041137764565,6.1564595111502,45.7444378926467 +15.7705896926978,6.45813067161965,45.5756056489777 +15.777049892922,6.73342557687579,45.6097920149058 +14.8330705328286,7.08243098977085,45.8607862541483 +14.7348789638225,7.11167750111901,46.4931340114757 +14.9403994191168,6.94838425766991,45.828035043745 +14.5056521763076,6.989926508145,47.099682173074 +14.6345366706951,7.05467531651993,46.6744205930673 +14.3530821826306,6.9108749628491,47.4160889275979 +14.1110401241485,6.96241186606339,46.2625169277071 +14.2157229220344,6.91554823490812,46.6468472963285 +13.9597689174465,7.12810441625373,45.9937841793874 +14.0226186351528,7.00002147050124,47.1533161034891 +13.894035388913,7.29306540910191,46.6210934209816 +13.8128179540358,7.49523311221494,48.112791719734 +13.7378944860657,7.56865264362148,48.8661387843622 +13.6349448144455,7.56703189707549,49.1412178829285 +13.5187336927726,7.50670209395096,49.3430083854871 +13.4149738358163,7.32756335505587,49.6198113247668 +13.2935779898091,7.10190132404629,49.3614360593546 +13.2077748344041,6.85258499372224,49.5549890810905 +13.1109448859546,6.67181574336221,48.6104355921929 +13.0268327073395,6.64766905938861,49.1333226009073 +12.9302936070446,6.72176373683718,49.9819161763644 +12.9038501176634,6.81991927833464,49.2142535641161 +13.1285605892448,7.31395716925967,48.2375825147333 +13.0454415873154,7.42739784626145,48.7767976079396 +13.2644471052494,6.91843967820089,47.3035274705903 +13.1863526949306,7.11152863187838,47.9973850856625 +13.3377974402809,6.68785087637322,47.0209236696552 +13.4962950660131,6.43307398805957,45.9375106820312 +13.4051329816073,6.5584432511136,45.9215103129918 +13.6237318759347,6.37674044922749,45.1242525429794 +13.9704313403999,6.56123759814247,45.1685039858077 +13.9964828176998,6.604917549647,46.75230158025 +13.9693704261987,6.5897863147864,45.6656170916895 +14.4257345109104,6.5196536696334,47.8592123304123 +14.0433326937924,6.42836882492098,47.6941100013182 +14.0605182443878,6.35298344490448,47.5546015443847 +14.1467962986945,6.35420898278135,47.8733656559462 +14.2876475507773,6.41349235613136,47.7340561926177 +14.3921373387886,6.54983405193734,46.9919942495239 +14.4496773086923,6.77915524499501,46.0198306349462 +14.5025840381654,7.02669747194671,45.4531646764947 +14.4144975022336,7.38481093774172,45.6525564294229 +14.4260241320178,7.20316608624173,47.1989832307989 +14.4841711391168,6.97842098557618,48.5172975801046 +14.572826215358,6.83480280578547,49.6816677842487 +14.4801684591506,5.87027973008838,-46.8960543734681 +14.4449336232244,5.91516658916657,-45.5933134788409 +14.5569903282697,5.77867651598044,-48.0642821068466 +14.6332715886182,5.86646781804119,-48.9343418791802 +14.6951256957915,5.87971789177315,-49.6776119550202 +14.862255698992,6.19141144621909,-49.248030127903 +14.8686965588334,6.55530882503179,-49.7453486903015 +14.8759835234149,6.67943900262208,-49.5840035212215 +14.8698523276643,6.27290523974167,-49.2763292890129 +14.9136467532959,6.76720661634934,-49.3666267640936 +14.9433851273795,6.74448441323722,-47.8238012909634 +14.9421002692771,6.68258302373739,-46.784089577311 +14.9361515720504,6.78810624319533,-49.3405234605746 +14.8741196709084,7.64858058088877,48.497237897038 +14.8234219021477,7.66207697052902,49.5779243115062 +14.7783065113558,7.661157246002,47.7404505130941 +14.862181633604,7.70982031084148,48.4749352772661 +13.9677481479118,5.97305523250391,-45.3089367439463 +13.961495097007,5.98614592852437,-46.9599906452098 +13.9601892245124,6.00356802872905,-47.5190842393164 +13.9485718692997,5.97166488835484,-48.3844695648029 +13.9580642813905,5.97895857472795,-45.9364821749725 +13.9380319488879,5.96685463974891,-48.9752049592065 +14.7174985524236,7.62828728152293,46.9380003016794 +14.6669751045183,7.58705882695434,45.6400404016573 +13.9202387665111,5.97315020024261,-48.9714511321873 +13.8839640892789,5.94916601046124,-48.6534559084545 +13.844150580825,5.84033716896194,-48.2930407045278 +13.9007103238128,6.01508691324653,-48.9791481504447 +13.8604637606093,5.88531934744936,-48.3369352529028 +13.8007822838166,5.8281286428301,-48.3693939591758 +13.7379144243711,5.91355450086322,-47.9911764039961 +13.7130245956734,5.87529617539039,-46.8511380463787 +13.6792400828744,5.85920931269349,-46.6051042068002 +13.6402688524664,6.03766801655194,-46.2915453004874 +13.5883274527513,6.23443725003021,-46.4519621192293 +13.5177614849015,6.25895812311161,-47.3780817293335 +13.4375486373074,6.26760595891794,-48.8995478980659 +13.7523504153405,5.87678027800362,-48.1401654094949 +12.0809099092855,6.6895770550872,-49.7329328955971 +13.0430805904065,6.13457032243557,-49.7631085512909 +13.0757667721227,6.07366649869028,-49.8634540084503 +13.0734941165704,6.03592456190436,-49.3850835148224 +13.082796809016,6.01747466966361,-49.6071080199481 +13.065581938256,6.39437750770979,-49.4921032236219 +13.0392094654562,6.51308518741188,-48.2361034035578 +13.0339107393871,6.70070725174862,-47.376923913664 +12.0422296914706,6.5842903798467,-48.6146204857416 +12.0115080322716,6.45323392846678,-48.2593922910982 +12.0258975278641,6.25934498514577,-47.5649899361375 +12.0491891818342,6.21556253670956,-46.708254973765 +12.145352135915,6.23202926001832,-46.3065136060613 +13.0693333992898,6.19740308242989,-49.681168959515 +12.187389202166,6.24264723806777,-45.0122914127804 +12.2600345063134,6.35902303575648,-46.6586589213122 +12.4503860656193,6.73084405265967,-46.7418706114463 +12.3631500401685,6.5334984089301,-46.5989676676449 +12.5263292302295,6.91807067562634,-47.4380273730384 +12.5970654061229,7.08649792845801,-47.2751066404202 +12.6984812577671,7.12241527419892,-48.8267767937075 +12.9401311379015,7.15428922330099,-48.4054828041514 +13.3772211201343,7.14486509404285,-48.3664067407357 +13.1917508951899,7.16737240731462,-47.6684345506651 +13.0861120740574,6.83037330827629,-45.0530051499185 +13.0527968962797,6.82594160906013,-46.2193909937966 +13.5495282776348,7.16490600811815,-48.1495692970677 +13.4973728960579,7.17396781054666,-48.4154616085589 +13.6591110473405,7.18296204327545,-48.6677160007808 +13.6108680987399,7.17064439245951,-48.041137009441 +13.7480541110371,7.19966113056699,-48.9230263854268 +14.0138176568051,7.19677944712838,-49.1936167127745 +13.9415501630002,7.17824057169105,-48.8550090149867 +14.0644033396902,7.18453551356003,-49.3200957482186 +14.161955930454,7.09311818030948,-49.3547311755053 +13.8190514217464,7.24484127994696,-48.3140927743895 +14.422187904806,6.9424056161995,-49.6457965783151 +14.4924166465292,6.90674580871709,-49.4533515519834 +14.5392303136387,6.82249247812787,-49.5370084097608 +14.5437550462784,6.82530069220042,-49.1090768613951 +14.2631413268915,7.08365099068739,-49.2958825496623 +14.5373047347562,6.84985086945439,-49.5326372782898 +15.3565618579639,7.56403179623195,-49.9953336753059 +15.4678588536261,7.75214386780733,-49.9057186669315 +12.8298371118491,5.75654181209891,-45.5865227215572 +12.7848799467367,5.71297783569389,-45.9822987108631 +12.7319454135229,5.78788488599194,-45.8252552212207 +12.6912726429927,5.79762379222875,-45.522197083484 +12.6486814517089,5.80843278850853,-45.3665297799387 +11.7631318463651,6.13429124185495,45.7968096208906 +11.6296225808193,6.19803900590254,46.9512595696768 +11.5339920760691,6.24451379505642,45.8549409953624 +11.0238687666756,7.22297138946369,46.6219673224203 +11.0046864435973,7.47390420500512,49.3081207648448 +11.7095372248355,6.1769010042973,46.7179555610297 +10.9963070758372,7.50139921702009,49.2003525383415 +10.9830126031472,7.45102525020089,48.424319519129 +10.9305012046762,7.07616223547278,45.0194851780788 +12.0685428618586,6.71054396544449,45.3933389366524 +12.1550041940405,6.6577370889574,45.143736149777 +12.2523615390595,6.19473547807046,45.383685485098 +12.1829729010006,6.38945928037466,45.5982812082627 +12.11660104419,6.66471908549658,45.3201226870786 +12.0564362371828,6.98437930505089,45.1618298003497 +11.7450937475082,7.35737914164523,45.8049698368874 +11.783071811223,7.41144484136078,47.377356184153 +11.8016456563314,7.43060389848429,48.0816683086677 +11.8091012967843,7.45276481321814,47.193860378839 +11.7181087782054,7.39827997677808,48.8324717491054 +11.7749347423004,7.41640963376625,48.4674821426793 +11.645913139783,7.41899748171016,49.2265202601366 +11.5833610593223,7.40599108322722,48.8515382360036 +11.4636916127922,7.52426638336155,49.9603088856931 +11.4577248829874,7.47236760331974,49.3910499248971 +11.4611397564505,7.36707252458205,48.1269799133204 +11.4723498218056,7.30288807236921,47.4409039680649 +11.4749147230431,7.29364051248909,47.2732743934085 +11.4103564572879,7.34494895735641,47.2701144618003 +11.3454061962424,7.42946721373703,47.172104095515 +11.2809728805361,7.58082166668259,46.9036345747383 +11.1449385223499,7.7854390392447,47.3052651086174 +11.2024789250826,7.66182582968194,47.1194884122255 +12.8427213123947,7.20152957672253,45.9868378770587 +12.8092990851085,7.20240527099068,46.2786001120779 +11.0878156591407,7.84683982816744,47.589898929187 +11.0199305884788,7.90358425873408,49.9051092156203 +11.0536053559634,7.91458461585816,48.7089517679806 +10.8411908367141,7.55135489251863,49.5944966969604 +10.8139698430599,7.64380740537512,48.2899860829001 +10.7400347082292,7.96945012932443,46.8493950988436 +10.7765205700331,7.7975075526273,47.1605877053466 +10.7180415156191,8.15407013644962,47.3915437074826 +10.7218555479427,8.10060094182352,46.9431511876301 +10.7401997517978,8.04223863278134,48.2008897623747 +10.7324391394746,8.13910455393878,48.2089217572932 +12.8081911436412,7.13259073051287,46.3498863066047 +10.7107596980298,7.80603545816974,47.1551369514401 +10.672570228441,7.65943697598845,45.8776891093913 +10.7200758447513,7.92846567639926,47.4685688562059 +10.6299248154062,7.54911934360502,45.858949575175 +10.6047355995692,7.57187841827518,45.9314564725878 +10.5990285091028,7.64860775942481,45.8906154118474 +10.5972964866437,7.7122794609713,46.0567031174807 +10.618177916909,7.81480278526818,46.1671781150553 +10.6637180904627,7.98656481152229,46.3188644219512 +10.6913062252224,8.16974612032905,45.8596845291594 +10.7164439681043,8.27708985770734,45.5580393574255 +10.726526549909,8.33443839445392,46.0161678222792 +10.7481711804137,8.36057952246099,46.2487337235977 +10.7377817887592,8.26256042676188,45.000114465364 +10.7223140193578,8.12291869922552,45.654276174293 +14.774536219966,7.53824669434864,47.721541402839 +10.7395064293733,7.97554221006241,45.0380402661519 +10.7719266808064,7.82350039000157,45.1658646717477 +10.8885473021642,7.53180020623627,45.2295942396074 +10.9660161378643,7.47653261597675,45.2090698286938 +14.3101374909535,7.76904087579105,49.4340790010778 +11.6593996260583,7.88691785780837,45.5660854171573 +11.722450787514,7.96167991349881,47.1627689864665 +11.7635121015845,8.02876092149023,47.7273522399198 +11.7869118412078,8.03691361953965,48.1173883900523 +11.773417149738,8.01465304558134,46.5867781582998 +11.7244542226978,7.99976400206347,45.6692890619243 +11.6757740613345,7.98301491665091,45.1421066178045 +11.6694740577975,7.95367698754847,46.2995309406083 +11.7055984342637,7.91139824926283,48.7870361778173 +14.2620476697112,7.760623815575,48.6746557195427 +14.0993698712771,7.5120526327652,47.9328951452206 +14.0171866604382,7.33388804983377,47.3830784523197 +13.9188594227703,7.08493584606877,45.688246015363 +14.1617550291971,7.61742519006382,48.3732609783304 +12.6854123956769,6.65155988853899,45.5979187766377 +12.5952985518203,6.73352448718277,46.4258879958816 +12.3691593748467,6.84661168606415,48.2476878820115 +12.4947824127114,6.77581466987536,47.4735063522255 +12.0641203099058,6.95604856734996,47.1383376760057 +12.2298739512907,6.95114334032942,48.4659891795897 +11.8952361740472,7.20865450546482,47.3045896430323 +11.9638563611779,6.99440395443783,45.9077997130271 +11.8803610595356,7.07336983629348,47.8596167661929 +11.8461088643577,7.11534779869068,47.572455094029 +12.0417283668894,7.00468795949531,48.6981504837931 +11.9383390331875,7.00985172849166,48.8958948867714 +12.082519683607,7.05362359651961,49.2050981074559 +12.2165283784623,7.10841096624293,47.4466299984189 +12.2851279195534,7.17650106678834,45.1676510254494 +12.1463417001127,6.98157887113187,47.4765875662806 +12.2582049666376,7.17875092022553,45.0752962860563 +12.2788894961151,7.05412826565046,45.4707852665309 +12.4135902932523,6.72703303877005,45.2321575660363 +12.3043224294008,6.95198716541331,46.2119732554774 +11.6753023210421,6.15066217309723,47.3347119654955 +11.8626898359601,6.97290303892378,45.4297227924237 +14.7244384743014,7.48005890691799,-45.3016163829404 +14.8795875692064,7.83597749958489,-45.5543437095532 +14.9432275983393,8.01266842421043,-46.5549458656343 +14.9650162598405,8.1166189229185,-47.3009510820993 +15.0182021932184,8.18492096220376,-47.704952979817 +15.149241571368,8.29119683359026,-48.8901828670606 +15.2238199021188,8.34366415984084,-49.1039679806418 +15.2774687383601,8.41156146304386,-49.2383797927414 +15.2659482741265,8.29521554496725,-48.9063802511943 +15.3216281682405,8.36456856002692,-49.8378695536976 +15.3054580902689,8.27148653404457,-49.0945572634818 +15.2533586483179,8.34783624102767,-49.6782910404698 +13.9744179641613,7.56901933731853,-48.8694225111805 +13.8440133246502,7.35300349347923,-48.5973051202896 +13.7282298397034,7.19142075184382,-48.8340992629406 +13.5978798039517,7.07197282795319,-49.8086840827107 +12.1067810026421,6.38023084584167,47.3582885254086 +12.0291137219656,6.36557933240469,49.7026513612191 +11.6696695185935,6.12234519539692,48.0371590546903 +11.5652532212739,6.14532657671636,45.2436611368397 +11.0159319237513,6.65110554967287,45.9050808018127 +11.065346534064,6.57143554753774,46.5296440709825 +11.1774532517736,6.45286231755709,45.8286907838855 +11.1143941609372,6.46034009532551,46.275298460014 +14.1221293010304,7.87073492721404,47.0265481378446 +14.200076829735,7.74700295120552,49.2363722523318 +14.0154365471678,8.03305672526061,46.4590084785515 +13.9184551193583,8.10726810562684,45.8729623870405 +13.9546927510201,8.08263615793602,47.1478737486705 +13.0371726824197,7.09877062082832,45.0985957214991 +13.0160876479402,6.88819412415607,45.6109507812401 +12.9778842603864,6.71900424168636,47.8650875053827 +12.9084873999921,6.67528278573241,46.6401477556451 +12.8484147403323,6.88296333508536,46.4267007759532 +12.8801849336767,6.73640978181687,46.6081616597155 +13.054962089186,6.9939544069352,46.004386817171 +13.1038705485781,7.11210756471511,49.8461046912502 +13.1214760741102,7.10127959674654,48.39935556109 +12.9309056152987,7.31204623821255,48.7092662806476 +12.7751766650876,7.14821132098182,48.5012265710301 +12.7161235477286,7.00983280529773,47.5422557317241 +12.8619999396372,7.32740023208165,49.0604017190404 +12.5514772975157,7.01355111544916,49.430298689462 +12.6485154250463,6.9312571490133,47.8005118438854 +12.3208156866093,7.61251907961029,49.4456801560732 +12.3842090785032,7.74653821448812,47.6235652758146 +12.4760367450582,7.77956616296911,47.2791029037052 +12.4270183695455,7.77965197470438,46.2429962994571 +12.5685985694745,7.63326437915892,46.4241492884368 +12.8220308853388,7.2389820514152,46.2384300334745 +12.6744000508559,7.43563871710402,45.646434779203 +13.0975815599888,7.02414853000869,47.046715937832 +12.9804202426158,7.09844204557174,46.7742582165644 +13.3705178315918,6.96922189219991,48.7994426699932 +13.1889538714493,6.93969095336387,48.5238857823162 +13.2726733067411,6.95106421180973,49.1057963571988 +15.146925181123,6.77716558793223,-45.9820496417419 +15.2378651347413,6.72696150171628,-46.4733762077168 +13.7379501821882,8.0687446226886,-49.3754272944672 +13.7144700915186,7.98305535573208,-47.6604709245597 +13.721509910142,7.84261430537796,-48.766358635572 +13.7265376035323,7.69132444127207,-49.219994780607 +13.7111318342591,8.01911019273828,-48.7090843645959 +13.7011749806882,7.48956283351333,-49.9359024916275 +13.6922118627484,7.40623090454618,-49.33614439079 +13.6660361939845,7.37754739773624,-48.5413995522532 +13.6298730410206,7.33725909505992,-46.1449112934384 +12.4523426466992,7.03593256721168,-47.5065489106874 +12.5549215951554,6.90726981590346,-46.8377371146804 +12.3390667695844,7.17290813627513,-47.6318460108821 +12.2585612562889,7.32701881292946,-49.4914079958077 +12.1874706573602,7.43217412878824,-48.2348048587324 +12.0851936890018,7.47436518172325,-48.3442220378825 +12.0121127848697,7.55652520199197,-48.2095613744779 +11.9370375766446,7.40335877521288,-49.1428401753113 +11.9269970178096,7.27247169892377,-49.6617590745991 +11.9061183324721,7.16699848552155,-49.7286407187378 +11.8848926901924,7.14860746776098,-48.922480093904 +11.8723338300452,7.04484007544924,-49.5898660216488 +11.8990706046711,6.99276849322484,-49.0033680183754 +11.933979956895,7.04022996528878,-48.7936362678787 +11.966921198562,6.99674393225309,-47.6805809090965 +14.1956465578344,7.97084016485887,47.9007227664909 +14.052133343903,7.84710478870105,48.1016270398329 +14.3651362698682,8.0049314944651,47.2458291761073 +14.5243760805859,7.97770559634526,45.662348161261 +15.0257232545146,7.98421902309893,48.0274882208807 +15.1414549013022,7.98523121462601,49.3235684888263 +15.3815650594335,7.74930239840893,49.1224415831853 +15.4217354602869,7.70107033809709,47.5947971860506 +15.4721076579799,7.63823983424395,45.3317802084095 +15.7640518666592,7.54892717158147,45.6657134756013 +15.6576259520682,7.6764319242072,46.9771864141624 +15.5919776991961,7.76923382103734,48.8754250718306 +15.4075934049175,8.01176268491336,47.8009303570931 +15.5064476850702,7.82033762382352,49.4671122787691 +15.1628936637989,8.07268823634,45.5274993248853 +15.2798006459183,8.0350707057854,46.6292829170972 +14.8492192821426,8.12800255667112,46.4709173195796 +14.2210998185982,7.51817480437737,48.0920314159608 +14.1067161936149,7.45021700865869,48.0331609578849 +13.9876086753381,7.29830783147136,49.9038994210845 +13.4215559157258,7.36393035635173,47.8637092039396 +13.377959237887,7.41067515547874,47.8591276622969 +13.3273584592945,7.38070170692826,47.9883223351597 +13.2384927938705,7.31302892926616,46.9883061021571 +13.0303041231078,7.25811692884456,46.9316873398982 +12.9778140002509,7.39415799905417,45.9435863034535 +12.9310887329869,7.45533317109533,46.2248118989649 +12.9210290089591,7.40324629775129,45.8562453462014 +12.8939335311911,7.34811890565778,46.8735945614633 +12.7949498290157,7.34171523627546,45.5174702006347 +12.7977570655539,7.513657704385,47.0028391728814 +12.8000729229697,7.39977657706653,46.1657242552065 +12.8392266867438,7.59265707372041,47.7493071207803 +12.8288503616497,7.64413664304811,47.0350310253214 +12.7934663778838,7.33501870989035,49.5323687362429 +12.8381422148472,6.92817954896231,-45.7958846833156 +12.9207908197438,6.98447255142058,-45.2108090662223 +13.1370544687971,6.91387251310071,-47.3677809726033 +13.1706453339385,7.06792491478412,-45.1848726514982 +13.1535442403018,6.8338258142877,-49.5610956829275 +13.2221910597208,6.97142618678468,-48.1301752985054 +13.227435115636,6.87726576451511,-49.9976384765108 +13.2047654563375,6.97337494815373,-47.1533501584769 +13.1985800777053,6.90079261253564,-45.3341983558972 +13.2933524179549,6.45913702791312,-45.7206863872637 +13.3987450135413,6.10087961238188,-49.9978474469819 +13.5245766922736,6.14408420329906,-47.5265083836606 +13.3279802254356,6.37369561050991,-47.2123453130604 +13.3529297609225,6.20299065353508,-48.9220168732934 +13.6947308261975,6.47139617055876,-48.4953509234943 +13.713455348239,6.85741051713402,-48.651053561637 +13.7384573448206,7.11793290111769,-49.2450723866445 +13.639740326929,6.23492027443017,-49.7780901280593 +13.7211508396181,6.67794776674301,-47.805687176289 +14.8802992233397,7.25076509886189,-49.5960430178228 +14.9731224468784,7.06589647620638,-48.5325736007939 +14.9938686021757,7.02609895272956,-47.9566618036765 +14.9947039139806,7.04989300093522,-47.0699369822065 +14.9408203631356,7.14062738925996,-49.1473170332973 +14.965526553062,7.08262875169294,-47.1011347431743 +14.8825199274066,7.21632277772796,-46.7454361801922 +14.9196896947362,7.17826271524554,-46.6846940017949 +14.7436714824115,7.28828216811322,-48.2346972570317 +14.8176430142543,7.22895188191683,-46.9949828673911 +14.6769759944773,7.22104327639978,-48.4667416852335 +14.6263112849408,7.13817671424943,-49.4712773658464 +14.6556466795564,7.21523248094687,-48.5174965796789 +15.613184828068,6.83691979143094,48.1290582038144 +14.8742759905127,7.11676363910586,45.3204313474337 +14.9099427982665,7.21776690887966,45.1826858465111 +15.0389607766245,6.29981080035571,-48.9836007439645 +15.0337817235442,6.31001297015116,-48.6151669203514 +15.0712271853026,6.29418535362488,-49.1004390245297 +15.0957600783564,6.02630044310902,-48.4421139857719 +15.0915131556343,6.14624472976672,-49.0601671249186 +15.0684959484665,5.92775064739318,-48.6303417677628 +14.953682352482,5.88423181125181,-49.9301636908331 +15.0287440348636,5.8600487633736,-49.443122393342 +14.6343314231094,6.36213946990383,-49.757768473816 +14.6019266060705,6.42361656160687,-48.3213142682434 +14.6346379942829,6.56371387419006,-46.0605276570593 +14.7623783938005,7.13074394484418,-45.4230335781986 +14.7532042688624,7.09187240389572,-46.9494390189543 +14.7470535463651,7.07409147489424,-48.0944274134391 +14.7480119674891,6.9925684880426,-48.4196151938389 +14.7979190964547,6.951414053146,-47.7413164708335 +14.866345489248,6.94449200755505,-47.6011439260277 +15.029243019015,6.88289664409427,-46.4951810421864 +14.9513775496034,6.90311298160086,-47.4224598334548 +15.0984305506346,6.85235796552593,-48.0874667434661 +15.089875624932,6.82365655772394,-48.4228041076424 +15.0730216289663,6.87826488835943,-47.840020556866 +15.0489496524728,6.82062143955146,-48.1268010869424 +14.99568554274,6.87113000130938,-47.7202716909336 +15.0210198695447,6.79192828143819,-47.9977284588705 +15.0203906604669,6.82663780631211,-48.0135181073111 +15.0106190178364,6.80123429973814,-47.4475877391563 +14.9752286655092,6.78204907237522,-47.3543895222635 +14.9319337288756,6.72757271845633,-46.9596559359855 +14.8948317537376,6.7270763875029,-46.1315377413799 +14.8590086916009,6.70304553968945,-45.7201552995089 +14.8081051803739,6.78700427607752,-45.1684938005296 +14.7772140187286,6.75864391189855,-45.1509100552628 +14.7693378006603,6.8386148008651,-45.7071979651 +14.775540581583,6.97084347780183,-45.3464982637265 +14.6462404565475,7.48762894394788,-45.0036901169038 +14.5914115161181,7.60545802487489,-46.0111569550663 +14.3971293939195,7.77757827694061,-47.7478463699481 +14.4291887185932,7.83014708131028,-47.9213713282737 +14.4814420317556,7.75263386190422,-47.2906643821194 +14.3816616622178,7.96290230404938,-47.1046573463333 +14.2492716509359,7.85562718539856,-47.4975696203461 +14.3672270405195,7.95765783590501,-47.7184800372185 +14.3328431111328,7.81705717795457,-48.8721897050885 +14.3588349549666,7.95764951568471,-48.2620183088345 +14.2907241271901,7.74114170440049,-49.3096248673532 +14.2409913783232,7.48024911538017,-49.7184597883243 +14.2524499440878,7.53120301691974,-49.9039857065657 +14.5762956859635,8.15794912493325,-48.1763579035528 +14.5706449011962,8.2717520992445,-47.9750918060705 +14.5650380830564,8.0957121775316,-49.3534022610422 +14.4890132104517,8.35759133229324,-47.4321135656127 +14.5405484539212,8.36610790132671,-47.3836448614054 +14.3372946876593,8.40986640992314,-47.7339793340606 +14.4178394330251,8.41093938928575,-47.4915020123131 +14.2862451725924,8.32750944733294,-46.4403924050817 +14.2670892655377,8.17423054947425,-46.1508588885601 +14.301597337871,8.44686350170649,-47.6725797741292 +14.2307553530832,8.05241716512376,-45.2829731670334 +14.2088110703866,7.91242055953476,-45.6217705042722 +14.2663842669646,7.5455300414745,-46.9576003875473 +14.2384644656615,7.73212967808509,-46.4567187517872 +14.255886555485,7.44790382501946,-48.190016325733 +14.2376879251676,7.41141605646928,-49.1206870219596 +14.2316063837289,7.45277258121957,-49.8810125147822 +13.4742683144951,6.48872892881754,-47.4375324234164 +13.4985649431371,6.52693393687987,-47.6915083833201 +13.6158265263872,6.70695984519254,-46.9313983443633 +13.6607533764486,6.64878176303966,-46.8600396778155 +13.5545242990766,6.56893532280966,-47.1603846426399 +13.7198022101415,7.53396853808497,-47.8903899171913 +13.74496216046,7.70492625112132,-47.0673744780499 +13.7511425365571,7.62285887750943,-45.9675595724225 +13.7029482365884,7.3918563317207,-47.8540219972726 +13.6789730248605,6.9824634720488,-47.0108683228048 +13.6189832917222,7.89640449443817,-49.4320184240127 +13.6703653734299,8.00852121507674,-49.5459647759859 +13.2284075608287,7.17390393091701,-49.6311651641028 +13.1457969783495,7.12195306906856,-49.5513802331872 +13.7421495750661,7.16491911490892,-45.4776450703995 +13.7173765196477,6.98443974324519,-46.4528455325622 +13.6992042227499,7.0961303434614,-46.8237965465709 +13.7152012733735,7.33456627222499,-46.1495892005961 +13.69724314003,7.21281224620643,-47.7444724155864 +13.7190789065941,6.37129669847401,-48.5238408211045 +13.4257361440754,6.09403161135518,-49.7361165584011 +13.3898192640819,6.17334827878131,-47.8922560906214 +13.3238743066639,6.47380022819859,-45.230289693252 +13.2571621957152,6.99134810062201,-45.2010287173478 +13.1633693695487,7.33159701538122,-46.0943418644152 +13.0761605203261,7.7169755528708,-47.3445743730315 +12.955386035353,8.04781144687722,-48.5875307704083 +12.7479919663284,8.14806187805866,-47.0479591901543 +12.8615637877638,8.09720766264612,-48.5786890407302 +12.6428296994674,7.90767132097848,-45.256522295545 +12.2880416892708,7.87966244805539,-46.4847494351456 +12.1643753191008,8.18439423624472,-47.1650166917944 +12.2159157598374,7.98662973203596,-47.8340855747148 +12.0043916810482,7.37122165023877,-46.3605783305512 +12.0644224006713,7.34400273368832,-49.4409405974334 +12.3609123333164,7.55213902894097,46.5707711547053 +12.3577066792273,7.50144630846,45.3207376592696 +12.3206570802218,7.4391758277342,49.516016783867 +12.3471888918577,7.49793321098772,48.0190287313581 +11.5602149089559,5.98992158837901,46.976247011172 +11.5660034535309,6.15232219698808,49.0814943257832 +11.553419679454,6.0774856639845,48.4792248642968 +11.5660099849751,5.93558140991589,45.6187331172813 +11.3964421114113,6.50120580255754,46.3683083075462 +11.5432825642975,6.3277051800339,47.836535244397 +11.4847292706144,6.40205780451385,46.9210243952009 +11.5709865126543,6.22854064534071,49.1113592651702 +11.3045641715247,6.6724457103508,45.8214305253045 +11.1583070979388,7.32002941555742,45.5329414955587 +11.2537276523971,6.86691082655941,46.1838725450131 +11.1275380328953,7.55530798610044,45.3894872708028 +10.9920506629287,6.31217694675103,45.7113883860052 +11.0028025076931,6.23804300694126,45.6028682502602 +11.0451343351805,6.47042471045071,46.3975750018065 +11.0195713075633,6.34325095689086,45.8962433333829 +11.2031076640854,7.07337202056481,45.3726384925133 +11.3123307062603,6.80269556169299,49.0468195516879 +11.3011502893936,7.09652202022109,46.7071487965351 +11.2959361146916,6.90759813131884,47.3835264672125 +11.2937844009615,7.33324957551666,45.596232248386 +11.0746231234764,6.66294402536697,45.9381638874504 +11.1242890633434,6.83691540394007,45.20298446979 +11.171015696751,6.91544772498479,45.400180569198 +11.2051794500499,6.90020854392467,45.0628335695858 +11.2127935396717,6.82035279901047,45.6536707283287 +11.2192162978521,6.69003034866415,45.33141599073 +11.2215117222643,6.69291519788695,45.2800295991537 +11.2102398302854,6.98517050700273,45.0098922027859 +11.3090659763749,6.19087109475239,45.5360790514018 +11.3177024240409,6.10786990225345,46.0157658084219 +11.0783117024312,6.37351659106688,45.1707490724355 +11.0667970478368,6.61798198341499,45.7817886622677 +11.3166572690939,6.18689706058556,45.9786633655566 +11.034316345722,6.6900795683721,45.3280559150301 +10.9855444864053,6.71963070317316,45.7755266217486 +10.9343480496917,6.60416303869001,45.3531933532155 +10.894143771231,6.44525374716264,45.2711801216398 +10.8148172977766,6.45595489358942,45.3059244652666 +10.8293464283207,6.38044845583165,45.2338475028896 +12.4492271306258,6.85244718889919,46.1772748910795 +12.3420495231423,6.85493377828577,48.8489845954642 +11.6407497964091,7.48393813615747,49.6828365739711 +11.6249804451884,7.42604574789627,48.6807442039708 +11.6459606638336,7.29316478620116,47.4605778932034 +11.6695670798918,7.22224362160679,49.6417639677185 +10.9167379367009,5.80196953004687,-46.483639446177 +10.8230364321236,5.74696207176884,-46.6766899867883 +10.7739879536436,5.7318367608768,-47.3587857340866 +10.7530640309879,5.9181396619745,-47.3711391803168 +10.7479544827333,6.10573080703952,-47.5151036396238 +10.8164135335794,6.41504208300596,-46.8446820585635 +10.8572140260162,6.43072968382522,-46.221585727751 +10.9757427349572,6.28323197658394,-45.7144397832853 +10.9883887932284,5.89681820945078,-45.519800123543 +10.8893425884137,5.82248540484176,-46.1389702937688 +10.9304597425841,6.21199662675386,-45.9952280812094 +10.9661879730552,6.22663162670408,-46.6999610079344 +10.8782462977375,6.17751391688012,-45.9844768880881 +10.8838790339906,6.23463834054014,-45.5020452294565 +10.9128873796239,6.19322537300289,-45.5545227649162 +10.9737439298406,6.31672427307657,-45.6724948645968 +11.1200548278655,6.60857816627973,-45.3594582928454 +11.0868523637973,6.49555144059442,-45.719774953884 +11.1526502726587,6.63445956619001,-45.6526223182769 +11.1340259364181,6.56579057632627,-45.4796458061706 +11.1928374427649,6.48482843055879,-46.0074257652799 +11.2327931954683,6.53764943233466,-46.2849328080231 +11.1672433480996,6.54403623498673,-45.6683923480982 +11.2763702216517,6.48479717072366,-46.1672919081496 +11.3722767418067,6.45670408768819,-45.1123093748963 +11.4647506128248,6.41103145109497,-45.3862845858787 +11.3327584997777,6.48541539401783,-45.9011981180487 +11.6130010238503,6.19200760672429,-46.6192569672756 +11.5459380264435,6.285371114707,-45.622189753402 +11.6944538010556,5.99576597330534,-47.6017373292823 +11.8076989052734,5.78063437788316,-49.9542663325838 +11.7593647845575,5.80746456385306,-48.536792611645 +10.4728128676854,5.76833058736293,-45.4215133733309 +10.4816284846902,5.77534939746344,-45.9007247030024 +10.4887828446898,5.80868828498817,-45.7829324062521 +10.5028820707474,5.89184270207392,-45.7562038108614 +10.5004206824152,6.12119669173941,-45.0529648386135 +10.488899503764,5.93227096758087,-45.1962583642315 +10.4952374247104,5.95368808877715,-45.4661091730835 +10.4768063353289,5.88847788246044,-46.9571294423515 +10.4371467360009,5.78477288663422,-48.9420006635376 +10.3622839639816,5.41109002405435,-48.15214339278 +10.3303841406791,5.42489938361413,-49.2576250424056 +10.4130929810613,5.34855892372133,-47.0623580682979 +10.4253094152741,5.37970904998061,-45.3631618345173 +10.3790617819449,5.43526249530174,-46.9552079410193 +10.8274431277466,5.9219798621444,-45.7252291834432 +10.8771397782075,5.9529203391504,-48.3271566678006 +10.8718248518529,5.97344942498271,-48.0711689385719 +10.8451007763602,5.99262103252167,-49.7759530365614 +10.7274787537328,5.91046245732643,-47.2948911075299 +10.6945937138797,5.98437226283594,-46.1111465205043 +10.7604101058612,5.91249502914112,-49.2633878776619 +10.6640824226786,6.01045559004985,-45.3754017533234 +10.8126260376894,5.9674009337032,-49.6360241718905 +12.0282397298283,6.67389396365372,45.3326617760266 +10.5864562249852,5.99992015591779,-45.3901357931316 +10.5617262381887,5.93867135410469,-45.5386964314598 +10.5098259286125,5.91014770187086,-46.0504539426624 +10.4592057387464,5.85031673888344,-47.0399994019846 +10.3037026235454,5.92020728044877,-49.7108859544626 +10.383426334642,5.83555597990006,-46.8589100080845 +12.0773931312147,6.64996865726424,47.4370016853275 +9.79481041115977,5.97339225533897,-47.6630255224661 +9.84383305365347,5.95632628264474,-49.8734657567231 +9.82075882845967,5.98099647580169,-48.2320486426985 +9.7853678770033,5.97836091968103,-47.3352005517746 +9.76056997422398,5.7291176917513,-47.118067244572 +9.78014849648191,5.89890605667027,-47.6812231992758 +9.72839822685027,5.52816531009071,-47.3382661631078 +9.69991411037605,5.42143296656791,-47.9109219644602 +9.75087999096706,5.32711971388048,-46.9105134251692 +9.71647283503256,5.37897828195737,-47.7654514458171 +9.76640167365554,5.40598410909396,-47.0778232079656 +9.83395802112931,5.43425051619385,-47.296424329935 +9.73197966031512,5.35307121595403,-46.9499240001344 +9.79665277031559,5.48811888128751,-47.7415430492303 +9.88139559737279,5.3904525557526,-46.9363541718857 +9.91751041972465,5.43378886406135,-45.3365949033714 +9.94671793855841,5.43599555749269,-45.9041878190093 +10.0504679228938,5.45971914831509,-48.6634203083393 +9.9911938739587,5.44215298324748,-46.2585343431028 +10.0392349775131,5.416610963044,-47.4098133395614 +9.67031903343796,5.74145766625781,-48.2336893161369 +9.68343590822719,5.76197523343753,-46.4774163538447 +9.51242265615358,5.96726313219217,-45.3885709731198 +9.49646066287584,5.85611671329702,-47.5868854508344 +9.3491095369152,5.55123568418612,-49.8496897283783 +9.36775877953612,5.52856978596734,-48.9506771072045 +9.35131423233339,5.49271824233735,-49.6265754290633 +9.9660385519208,5.57542636267937,-49.4360729317837 +10.2255631969869,5.70562296323322,-49.248275340798 +10.3209684415958,5.75932212631342,-48.8952152473951 +10.0985700190746,5.65788605144778,-49.5429302552968 +10.3625585919385,5.8969535784023,-47.8032834973417 +10.3928514004969,5.93055165963597,-46.3019637730914 +11.4501131653921,5.83190240006194,-45.4355966625682 +11.3832462292382,5.72961571565863,-48.5686091251602 +11.2520229873614,6.67295929815531,-49.7398956423786 +11.2910550138046,6.68781244253215,-48.9129123414453 +11.3362499190121,6.70374832509281,-48.6979925013207 +11.3837982222481,6.73814691949504,-49.5570300809167 +11.441131768805,7.05226680699407,-45.0854242027683 +11.6200570023498,6.82952485560453,-45.0521685477926 +11.653836704632,6.70896506624334,-47.7996580418191 +11.664105840419,6.47559441823158,-48.88280093668 +11.6528635136051,6.55014691595888,-49.9324718473686 +11.6693583954313,6.69077541689551,-48.7588215137398 +11.6664126239533,6.76655930714429,-49.3088123912189 +11.3595724521542,7.2130886525406,-47.5892134329995 +9.77674600297726,5.31269758811513,-45.8508457236947 +9.72488107980366,5.38709291154078,-46.9034945115432 +9.65787983171772,5.37436551411869,-49.0039199585747 +9.39389621508127,5.7174643193934,-48.1657848667492 +9.48175490273963,5.68301018575203,-49.1252419481114 +9.44212070386445,5.75712243659057,-48.3670410387215 +11.4369100088847,6.28813970385006,-48.4733249441501 +11.400535162772,6.26702479277975,-47.7532341017488 +11.5110250631425,6.47093471450121,-49.3785400153438 +11.1707068955839,6.63603445513199,-46.693791005792 +11.0877392199324,6.78222818778297,-46.6112221976334 +11.25936000151,6.42052579050208,-46.1693828692401 +11.3441650695904,6.39676927949251,-47.192522291672 +11.0629884127985,6.98435465229819,-47.9278661334174 +11.0876721997378,7.16789721135791,-47.6879850173578 +11.0814708462744,7.1855688298197,-47.253534023227 +11.0739547353044,7.15171688911522,-46.9059981459683 +11.058995717449,6.98940746249495,-47.8048848746606 +11.0586301908412,6.95329576312205,-47.5581145395829 +9.34800118691079,5.73268182069752,-49.1388663505152 +9.27323991115176,5.70203436335268,-49.9864676085136 +14.2338280573277,6.40923642273326,48.8424401989272 +14.2019828845499,6.64034866145752,49.6163388013918 +14.5420107574874,6.09090932211617,45.4305563130127 +11.0266666807216,7.13298731420014,-45.633297396028 +11.0484007766915,6.67816004576815,-48.8045465343841 +11.0375121653809,6.69774157051296,-47.2157049467436 +11.2886366037198,6.35647901571886,-49.858660525581 +14.6030903048349,5.97445296276069,46.9245107953444 +14.6486010788437,5.8022444755015,46.2128803409669 +14.6235971152163,5.87566141204297,46.0170443437177 +15.0035789260116,5.64676663148039,46.2634838807944 +15.0692795636063,5.68555571482725,46.3563870736501 +15.0834884040425,5.74772668678958,46.0489964075402 +13.9661678947459,6.34694221316414,47.6432322314712 +14.0491186547225,6.32361400142131,46.3003738912772 +13.9166459056327,6.33279070982224,48.8779429518619 +13.8963767475666,6.26682236334431,47.8159802888998 +13.8861810955261,6.26467020171212,49.0540204980199 +13.9112735698961,6.20655595474331,46.8355092497688 +14.0001890250077,6.2221613768834,45.1266298802505 +14.022072663457,6.26126064852153,49.6611042117952 +14.0251553027746,6.18756930031951,49.3827767630209 +14.1628276336705,5.94893959148124,48.1854477214031 +14.1299601095205,6.04152370172422,47.4216277149341 +14.145884862394,5.97798800953386,47.7079852816374 +14.1601139849266,6.03869718784002,48.016398185598 +14.1740360528521,6.15684106790289,47.8487862520095 +14.1954507156884,6.21992848306318,48.0381605972624 +14.2943916395278,6.27264924839639,47.3522579488497 +14.1774738166805,6.21174142666127,47.8016212599401 +14.2306283007932,6.27962373489605,47.7924417735307 +14.344185480216,6.21900660630996,48.0628452888394 +14.3925401523679,6.21828257137974,49.4764210207013 +11.4354000066577,5.39072634140739,45.3002336968896 +11.4081972508649,5.51182572152379,45.1112630723786 +11.4005772660352,5.66371817732256,45.3954958815832 +11.4082316858143,5.70249185463851,45.0253707662242 +14.8041700569607,6.37923461078793,48.8227902293882 +14.8999755322152,6.31594192130021,47.6604648110623 +11.3192821706396,5.86624532808137,47.3483445464374 +11.3131717124287,5.96152351212614,45.7339609052549 +11.2909385543,5.66635415419438,47.5177445833934 +11.2923718004095,5.72338554580109,48.2030508948764 +11.309208449539,5.742360646105,48.4519933250839 +11.2951630422719,5.64719321382145,47.9526290356594 +11.3778577226822,5.98868840209598,49.0954280695523 +11.3452718282753,5.8731527116017,48.5514059972806 +11.3882056670422,6.1165912690462,48.043000341362 +10.3238582232045,5.99880616733074,45.7983767026538 +10.3160948993677,6.11482507266452,47.8805386851396 +11.40191758365,6.10886834890569,47.5054261774283 +11.4532885382999,6.13386649010133,46.362707818483 +11.4202775887575,6.11197936078819,47.5787235051801 +11.4799038126623,6.15142583618066,48.3022492089486 +11.4648677243512,6.22934262496664,47.6322140613765 +14.992560608708,6.22682267238419,47.3205856895889 +15.050004860632,6.09138846986536,48.3663689599167 +15.1123655920185,6.03597126062003,49.8565015248004 +14.9820068528484,5.62820218057519,49.8479805804426 +15.000681009951,5.59578697305739,49.9366096085722 +15.0086811351717,5.70957239522682,48.3411498603296 +14.9673312357217,5.77590173648445,47.7805809121156 +14.9983150256046,5.61489559855995,49.2426424943178 +14.9042353815356,5.8300525457641,46.504275515536 +14.8251727488829,5.8871604117547,45.5284356967837 +10.128284941436,6.1133454817987,45.9296706150288 +10.2787246914031,6.14075371627147,47.8732831468628 +10.2042868108418,6.12672459069396,47.5035842538671 +9.84650962116993,5.91755749007998,45.085293893736 +9.90720968025359,5.79635465279769,46.8548560203854 +9.95676326328821,5.62982439470022,48.9958710771421 +10.0348059208184,5.54063532654803,49.7619932360537 +14.6343911314849,5.96447569233228,49.8049244567215 +14.6474089356169,6.04972635001765,49.1766043848313 +14.6153450456015,5.92224985847771,48.0157722552778 +14.5905089019118,5.8928496800716,48.648803842398 +14.6034065711138,5.9514964908543,46.4524483623572 +14.1284940108564,6.10663768845812,-49.8171280424082 +14.1590801827904,6.13387844458267,-49.6787896729317 +14.2151751080882,6.19776717321081,-49.4122620614085 +14.2859012013806,6.16675908755253,-48.6088001858801 +14.334863104085,6.10574505206735,-48.1414230507364 +14.3755385171879,6.10660585758328,-47.1228180596755 +15.481378605417,6.32268581035614,46.0483656376775 +15.5593499063108,6.40818949161904,46.0089986074169 +15.7276857879787,6.47686721429773,49.0265069183844 +15.7760893957894,6.46381798004548,49.9274350857537 +15.9312618949722,6.40535242099467,48.4526543015923 +16.0175891824279,6.36536010312732,45.9404346296986 +15.6630139627888,6.45384175209653,48.1994838087857 +16.2779295299359,6.17516647675079,45.4891325263047 +7.95222154782546,8.0561920604408,-45.0515706592985 +7.90134171671047,8.0673076492731,-45.0155867335804 +7.82444251403499,8.1967970025646,-45.7110559995166 +7.87785460639829,8.44165396957425,-47.1293041112591 +7.84957677748827,8.09553222237549,-45.2528970407501 +7.84690081739538,8.26760495333949,-45.8281436411934 +7.93305545215299,8.77215055390151,-49.016184976763 +7.90901688135908,8.60220274118408,-48.0987702301451 +15.0728360381243,6.87316743232657,-45.434353760615 +15.0535495826469,6.8135168161509,-47.1606784237849 +14.9994884573675,6.71199370472965,-48.8453503309946 +14.9707793988801,6.6253825047491,-48.7113690867876 +14.9450805300378,6.61607203616434,-48.6363653522053 +14.9648860723169,6.62884622992719,-46.383035500462 +15.0025492237453,6.70408904798115,-45.0956523278914 +15.0746528954987,6.73776437320574,-45.3841919654973 +14.9506422241257,6.58489984277777,-48.0465017498422 +15.0087913152164,6.61891678132349,-45.5680545237561 +14.9843684656995,6.5463846892997,-45.7569056855738 +14.8911552703625,6.3895835528371,-45.2799597082749 +14.910177511202,6.33736622811443,-45.6411445278037 +15.064452416068,6.3685399115171,-48.0721921901988 +14.9464015814446,6.3807507086207,-47.007728202085 +15.1078751912828,6.38192901547538,-49.5791435320433 +14.6678446170285,7.68766564227866,-48.272668043763 +14.7422469269664,7.63968000820011,-49.7062619868829 +14.5222952567965,7.75465657018474,-45.3449444714718 +14.6041546577539,7.70086051388686,-46.6654143319013 +13.3076587737196,7.13948032735156,-45.4988893500765 +13.2807251554867,7.002898158357,-48.1108313940722 +13.2807417811458,7.1170511047271,-47.3292581456837 +13.2969775016026,7.14517972977507,-46.2916970086042 +13.2771540552953,6.83345297529831,-48.2444870022732 +13.2748245675403,6.93946995666722,-48.3392422439749 +13.285951550217,6.81963240314402,-48.8354916880039 +13.2992218042981,6.79559311340207,-48.1777983772433 +13.2714627122725,6.88413423701518,-46.9945846354801 +13.2479306972635,6.81332172201877,-45.5452675364917 +13.2692728629007,6.87947497220946,-47.0639238038692 +13.053433214735,6.08293062674787,-49.0944596023031 +13.0656390572793,6.0688013290353,-45.0378726349095 +12.8970651102382,7.02834413634718,-49.4945027105227 +12.9522102127699,7.04474368335353,-49.3202001287646 +12.4746955749875,6.6239492842512,45.5189606243954 +12.4646585588048,6.52126390841972,47.7510160510042 +12.4614540238841,6.28278461731997,49.3221710762091 +12.5237960611255,6.1705114495781,47.5538769055134 +12.5888272473932,6.24535662602168,47.4768258353643 +12.658211232573,6.3824855734457,48.056396930835 +12.4406941267226,6.38337326371418,48.8538411077701 +12.7151627097524,6.41945569130346,48.873780344528 +13.428134861312,5.74805309163471,48.0544992801596 +13.4961864381438,5.73149241586365,46.7711104577424 +13.5738418044512,5.74405557133316,45.7577195159449 +13.6125974925587,6.06191009769173,46.587324654652 +13.6267636979883,5.89260851933289,45.9764323010727 +13.5837925251208,6.21833467625632,46.8957153738559 +12.6017480703613,7.35171171281117,-49.4120233704329 +12.7715422392717,7.12061050317197,48.225705030858 +12.8198621173431,7.08416513216448,48.1737918604556 +12.7979600561819,7.10891936744038,48.1774142265911 +13.0064240918673,7.00697530852851,46.5236836890153 +12.8811497457643,7.03613776262079,48.4027931643157 +12.9512448892793,6.97527160507893,47.9456807838391 +13.0334482861311,6.96350024645172,45.6798402866795 +13.5167377559214,6.52700618634293,46.7791905311524 +13.5165586829501,6.59630115645224,46.4219773878153 +13.4943322177066,6.56692025521832,45.3132838978924 +12.6908703507755,5.72035056455575,46.2761436123728 +12.9586924267771,5.77986531351737,49.9849078026253 +12.7337864473668,5.66991818755385,48.2108234117828 +14.0334740381915,7.08129023867552,48.6271246539621 +13.9625923412391,7.23298234815769,46.4633732503601 +14.3312347017722,6.69067157517329,49.6275245985721 +14.3759150470237,6.75714987161546,49.1338534678933 +14.4063350450368,6.73676326084554,48.431938800852 +14.4518060995537,6.66458209322712,49.4185954735445 +14.4828895879845,6.541894636175,48.9698555446454 +14.5721736140189,6.31798986387708,48.1338383774164 +14.593823161938,6.29513354001656,48.9148491463942 +14.5608220841745,6.71773212842743,46.9712197089453 +14.5679396968824,6.77732195715679,45.9404817920003 +14.5737898333481,6.58195552051633,46.8179604947018 +14.6054953107341,6.44010181995126,46.7955231686068 +14.5494842818713,6.08306612968267,46.240921664049 +16.0061908420427,7.55184337138966,49.4510927628326 +16.0113071131801,7.43967528350775,48.1544248294339 +16.0155331391368,7.50337431032545,49.4348258522944 +15.9940492913686,7.43772499294336,47.0095021605097 +16.0079186313014,7.38011880511647,47.7777484383464 +15.9976909506281,7.21599172295524,48.2190355160882 +15.811705606031,6.81445876838583,48.7266002328131 +15.8651035023726,6.96691808161337,48.3090930018508 +15.7018784685673,6.70008443555061,48.2167641272902 +15.9582332288383,7.12637999423727,47.9493196806488 +14.5063059562426,5.8509978901993,49.5573799516422 +14.5249641183519,5.99727511434606,47.4717771206076 +15.0065615628076,5.90897974140457,48.6606546645887 +15.0612359152265,6.06074752505735,45.7401007576626 +15.9663467184075,6.44883973377102,45.437816344916 +15.9335970397407,6.54467480777877,45.2510212586234 +15.607952248819,6.73103992557542,47.3437536489461 +15.5041049360263,6.71639140805708,46.496282825109 +15.462651928951,6.82554910375828,46.1807336854872 +15.0482006407011,6.37739342950805,46.3360679880627 +15.4017951143948,5.9497400913838,45.3005751338309 +15.3535662452717,6.01632773741143,45.9978719944481 +15.282232481302,6.06385605376357,46.380582758422 +15.1902848057353,6.17646642828613,45.2547732877217 +15.0081034358369,6.36956617732848,45.3648544336669 +14.9386373114807,6.36373417386156,45.6741600265766 +14.8860123083824,6.30762712369853,47.663685299551 +14.8128957764263,6.1716172957218,48.7802126181954 +14.8432910218964,6.25276297043251,48.5215453159216 +14.7810334861917,6.12107307228522,48.6568814048319 +14.7687023718158,6.17310238721431,46.9860455827819 +14.4232224287488,6.09793494075067,45.9798264865246 +14.4517780426215,6.15437903700744,47.4759151112666 +14.4892548462601,6.21836324194829,49.0099662624458 +13.9413731012056,5.74135214717124,49.0254689797954 +13.914943514921,5.86533390560217,48.4709945961695 +13.9705805869476,5.72348592001252,48.7264361835045 +13.9977549556907,5.80805811036637,49.0284511844589 +13.8614159065292,6.08913545852636,47.8715559035144 +13.8077984777323,6.24083225028803,48.3667950671008 +13.8895862862829,5.96408475863158,47.8635055909137 +13.7726054314769,6.26838381114681,48.5378074044609 +13.78005790227,6.29544195087393,48.3492866944481 +13.8636947217418,6.20694389851649,48.5358117216819 +13.805037887774,6.2219049480952,49.1562096623215 +13.8908147183562,6.28319776256439,49.1635871803541 +13.8627277872209,6.24558410810356,46.9822954880463 +13.8677528646064,6.30306550123247,47.5120648325899 +13.8461815235385,6.24066543516439,46.0086827407623 +13.8181385515514,6.1654280251397,45.0142814974301 +13.7917947715523,6.14654887148824,45.3532700611265 +13.7816247902536,6.16803272686455,45.6166346944722 +13.809591954358,6.35897883146707,48.9155379661804 +13.8111101371643,6.24618030528906,47.9057192624295 +13.882568946663,6.39162306374841,49.430677184515 +13.8766024272656,6.35210301874178,46.9197487015251 +14.3987138036807,7.14722597028917,-45.0950803145302 +14.4390990966217,7.42462359620481,-45.655641992365 +14.3970107216951,7.52468975273669,-45.7586417688009 +14.330069075586,7.72109020216742,-45.7488857818433 +14.2708955405194,7.89997161672415,-46.0511841037772 +14.2153514818835,7.96486978759989,-46.095241419598 +14.1641618912,8.14217580411712,-46.7510111451253 +14.1344544537465,8.10793772439135,-49.0160536322476 +14.1601653649518,8.07542520476627,-47.4833277722882 +13.0805618730069,6.13445254918123,-46.9868405848524 +13.0512317020145,6.25041646303292,-49.1976181192024 +13.2637965665114,6.10887878095532,-47.4059093735691 +13.3830485526947,5.97336158347947,-45.5037740993579 +13.8355369360898,5.65051967105463,-45.8807349397312 +13.1743437405581,6.25014754194587,-49.9004990098151 +13.9378866954526,5.77955828978316,-48.7150922575899 +13.8776427708552,5.7452270867775,-47.161628482091 +13.933207300226,6.09192153541803,-48.5555230058456 +13.8528153105109,6.2007943344444,-47.4157597109706 +13.7911483620247,6.27408857987534,-46.6372178252372 +13.7561193118811,6.25242057813223,-47.2342964847517 +13.7142264744013,6.32810435323505,-47.200512959949 +13.6807264377594,6.30915536806685,-46.8930525348241 +13.6702676751542,6.24979164548736,-47.3506896170913 +13.6401100632924,6.16203453577775,-47.8233738434731 +13.5567142270626,6.08424263612113,-49.0043274605506 +13.4890972648578,6.09427162966184,-49.5019852928914 +13.2344596132019,6.30277799952046,-48.4504432360926 +13.2401904041615,6.42783475104179,-47.3271927201672 +13.2229584041889,6.33521127458371,-46.6019083805221 +13.2601617104854,6.38339736488047,-46.8836743964119 +13.2897717951808,6.31026660280576,-48.4870918343261 +13.3271976036054,6.1890708041018,-48.8471529469583 +13.3874397478398,6.05042999504654,-49.8629662514831 +13.4431973673711,6.01864451527957,-49.9059829107408 +13.5328120623611,6.07756411836184,-47.6843319299669 +13.4946409400729,5.98832395784506,-48.8349000294445 +13.5783182408951,6.17145878401163,-46.5359592724483 +13.6016915373938,6.31403585647633,-45.8339475626983 +13.6069219062218,6.47033270061057,-45.7019488287297 +13.6354802391114,6.54093095714731,-45.7526520939637 +13.683193906594,6.71511463936565,-46.3160973276292 +13.7308256878408,6.72063547652041,-46.8188430195892 +13.7553452332448,6.64748852997757,-47.9319079301346 +13.7770117705911,6.66451449935242,-46.0469523495128 +13.7945503197074,6.55528448016975,-45.9761722068088 +13.8145676537921,6.40392057699686,-46.2045992351707 +13.8500108815082,6.31211147605611,49.0783007953752 +13.8632513772237,6.36199850439551,47.1312378914313 +13.8595608602335,6.25981870265828,46.8968070876857 +13.8393162082393,6.17869555902554,45.2230590585409 +13.8419530265875,6.09342157978284,45.3643785964299 +13.8481398976734,6.09473338953892,45.8223101596852 +13.8489629151743,6.03835642751912,45.9416799818069 +13.8457259525767,6.00822515630377,48.0602654039623 +13.9406541332435,6.10182630707038,48.8934409254976 +13.9615493910518,6.19531342557902,48.2416379772661 +13.9740025219556,6.275063416813,46.6531022117024 +13.8971856831502,6.08839542727535,47.0260389594759 +13.8437098484034,6.58772030129064,45.1808742316298 +13.7944921379231,6.58495900800234,45.7018672382137 +13.7637643246623,6.66158787753845,45.3095097566381 +13.8948480072497,6.29811076217181,46.0517984039176 +13.9667336454003,6.17332457193514,48.8349488184984 +12.9454344326822,6.01554278023526,-46.5550872427316 +12.8781284194357,6.11936684604346,-46.7144355164169 +12.7919849940945,6.23881167253897,-47.6446591377055 +12.6728093679911,6.19745831437367,-48.1659673469844 +12.622833045247,6.13796413071185,-47.3860455782264 +12.7130251726503,6.20198165188976,-48.2162115438059 +12.8433052647737,6.24106693814137,-46.6994246990593 +12.5693293381966,6.04513513840076,-47.1206682437547 +12.5165268394379,5.93701064893476,-46.9251463652069 +12.4452071214463,5.69900212527111,-47.980915758289 +12.4170325632309,5.63365556785812,-47.7716273415638 +12.4737754794732,5.85465090024933,-47.188935347298 +12.3727265460837,5.53081725327882,-49.1114536552583 +12.3351590359411,5.58526909707434,-48.7786280207874 +12.223100289035,5.6358285777778,-48.2856224648467 +12.2962806790919,5.59235270609892,-48.6711473736957 +12.2497114387892,5.59844911069504,-48.3963319819088 +12.2391158374887,5.62893942996735,-48.1773721311534 +12.3149649489953,5.8241773626836,-47.2459697908278 +12.3603217988642,5.84310632340113,-47.7176574243276 +12.261783090584,5.78763657502885,-47.4823898800265 +12.2340282806263,5.7703574508315,-47.7421665218128 +12.395116373337,5.73944650977623,-47.3372575668118 +12.4289887786265,5.77553183455313,-47.6048750662575 +12.4578340029705,5.73778813295169,-47.1724781460855 +12.513076679009,5.76129915308411,-47.1994137344307 +12.5843497968955,5.82879697040698,-45.8921666889865 +12.6549050171498,5.8411978392332,-46.4660650442396 +12.7391093882027,5.93163112993469,-45.6349868831352 +12.8662016556681,5.95853179172123,-45.7772371288708 +12.8024177541785,5.93406772585973,-45.6275598564911 +12.9437383078572,5.96813840009242,-46.3287358774672 +13.0002138243397,5.99213923004164,-46.9987838065053 +13.1480087403952,6.196644273364,-47.7102670957567 +13.0695201172731,6.09018913000876,-46.9924880889071 +13.2588851855083,6.37999824984282,-48.6513091600469 +13.2282672292587,6.33687426473131,-48.9441319243757 +13.2492508428684,6.58208214933969,-48.877060744988 +13.2310342473948,6.64651908945484,-48.1222628781136 +13.2648506407818,6.42723875237642,-49.0040646628491 +13.2605658003796,6.74513129284634,-47.0137170455179 +13.2563081349362,6.93493630943254,-47.3887462484086 +13.1721295134804,7.32936786836196,-45.2488494355694 +13.0518208302369,7.28990060332162,-45.915413090075 +13.2478838662044,7.07424171655358,-45.6262388289535 +13.1199143121939,7.37458114241929,-45.0217813293314 +12.9143122719197,7.00928208395437,-47.1424393391315 +12.8681694778252,6.83800524947218,-48.4006882096419 +12.7934730635572,6.86220523831958,-48.9511869195024 +12.6302252787193,7.11318262654777,-49.8174131835933 +12.558969773481,7.54482842497604,-49.5530098338365 +12.5892098004494,7.8465323172771,-49.4999089857546 +12.9767780383769,7.13483889409438,-46.497050625143 +12.5872005457258,8.20320089864522,-49.7038687714793 +12.51596803627,7.90603993462486,-49.1284067501907 +12.5297113122627,7.74680106656018,-49.5203157063199 +12.5216359878634,7.73621384311679,-49.315480620207 +12.5057410807971,7.81634508223297,-49.4935792496103 +12.5239382289104,7.70054347651447,-49.2809173918192 +12.6247623238589,8.00661623945115,-49.7617579029098 +12.624059468161,8.27979339159645,-49.6318902997136 +12.6253166688216,8.19011863854654,-49.7409326870101 +12.3514904593844,7.48519557000806,-48.827724821884 +12.3272114776854,7.59345657878228,-49.2601925788861 +12.3319157220477,7.70529656255496,-48.9333937764501 +12.3208918493887,7.77821297529427,-47.465252745014 +12.3132445642946,7.86934097182819,-46.9511183958458 +12.3238601935779,7.86273057901727,-46.7475287192639 +12.3303636180299,7.94602343573709,-46.7989822107965 +12.3303226746972,7.93903998374511,-46.5093488840849 +12.3214710499894,7.93319615501883,-48.0859229758131 +12.3796597988305,7.92947415387585,-49.1685514648969 +12.3418807301213,7.95906913362079,-48.8882721600638 +12.6159520884195,7.82319045529634,-49.3567976302914 +12.8348699479136,7.93116625892554,-49.1903144245537 +12.7265808769982,7.89536861339689,-49.0928136653182 +12.9166798530358,8.01460200104007,-49.3970999049587 +12.0298848762295,6.91442986875521,-46.50836206337 +11.9718470787461,6.86430275746577,-46.9299399134526 +11.9384285736682,6.67827001976985,-46.8574601758714 +11.8907822345123,6.54110225847746,-45.6746860755346 +11.8637639247779,6.3955307723673,-45.1808920841115 +11.8035755339284,6.96926193833292,-47.2489520911523 +11.7882957983265,6.96101698581755,-48.8615718497681 +11.5624934922417,6.38680214309694,-49.4212545284391 +11.5900124177076,6.5226351257383,-48.1242063853012 +8.64680860712718,5.7887694864216,48.4187942801877 +8.59389791686062,6.06238864819958,47.3781815695999 +8.61302277136389,5.88412958271779,46.7433704982209 +8.58940889328616,6.19580903357896,49.1010694376382 +8.52637599883244,6.34937601519672,49.1074741844978 +8.53967829947985,6.24384294955369,46.5584887170385 +8.52257924484176,6.28507439574377,47.7109358784662 +11.5539420547333,6.22058266713593,-49.7700654180836 +11.5571975975106,6.17374614948499,-49.8621184158304 +11.4782418534061,6.2199161091909,-48.4521598502577 +11.556522742714,6.14201228299387,-49.2165520510485 +11.5361367140003,6.15879642022534,-48.8439542427927 +11.4532724134086,6.29979153761588,-47.6605985342232 +11.4380343393446,6.41543298009573,-47.3867344624102 +11.4384437309851,6.35447351456981,-45.308750461931 +11.419525375339,6.22451976742356,-45.6514706900825 +11.4108410164464,5.95214998670107,-45.5163899165195 +11.4060129332567,6.08551699605116,-45.5093267399546 +11.5102201478034,5.72639572001409,-48.6500295432734 +11.4520612977346,5.78994561293435,-46.4136780759302 +11.6963833980774,5.17653569679175,-48.1489928091 +11.6545522365429,5.26643603845223,-49.2715266563033 +11.769013588201,4.94119254129167,-47.2743165277072 +11.7268916718472,5.05314845913036,-47.6191704361998 +11.8255368999954,4.85413425767511,-47.5484541184211 +11.9792008474271,4.72621311575413,-47.6236656470138 +12.0627839708305,4.68718547331164,-46.9802348652035 +12.1284873357342,4.74488186611642,-45.2595036867051 +11.8912814691628,4.75977922591643,-48.8368064660342 +12.3039244612312,5.00698603491708,-45.1162545017991 +12.3294158417946,4.93267806460992,-46.5294506756535 +12.3095918408904,4.93943406359984,-45.5757097719952 +12.3716184284975,5.09340311425596,-47.9273371899754 +12.325961250129,4.93867100361368,-45.3722008802152 +12.338215562716,5.04914980540965,-46.6756050229033 +14.2160935838804,5.53492337388922,-47.0311816269588 +14.2379213498,5.47995072443406,-46.9096782397031 +14.1932503944322,5.5623991132385,-48.9399781256146 +14.168782512396,5.72102216689833,-49.940206554404 +14.2361777128389,5.54898922741658,-47.7615462976033 +14.1898994998315,5.49686036447077,-47.6132797849297 +14.1631795068664,5.47175901016559,-46.8115264827923 +14.2179657093387,5.54199233698577,-47.593765580978 +14.1141803713226,5.4054628595512,-46.2235212404602 +14.1334998735572,5.47569639183414,-46.3205016274643 +14.0774662499994,5.42163124390111,-46.2073497595204 +14.0136585644361,5.60553908783603,-45.4293548074152 +14.0520317656289,5.49730229275898,-45.8570210182066 +13.9777994012644,5.65557362363861,-45.1334481835269 +13.3683453644733,5.25066808515135,-46.8621680487508 +13.4492400500975,5.30638393129121,-45.3503759317057 +13.2923438506426,5.1859812538478,-48.7510539237478 +12.0127762692397,6.56103335026899,45.1458383994804 +12.0755783905484,6.52660296758977,45.9634897165552 +12.1343517304433,6.39961412472443,46.3323353525822 +12.1878999335723,6.25190665946137,47.5207864037758 +12.2363254537519,6.03311527072107,48.5359113343953 +9.75299260081154,5.70947513320886,-45.9897600149955 +12.3720905804748,5.3662251503741,48.805320504911 +12.3978229315096,5.30208983494529,48.6754006589767 +12.4033781840684,5.26360327528002,48.6822476582445 +12.3210236927737,5.50206067086577,48.0097178210722 +12.412825111211,5.25681709117724,48.0048536071272 +12.407038547582,5.08627168537404,47.6334667285598 +12.3829716046295,4.97565618663324,47.3916310677681 +12.3847258671214,4.86034165856098,46.5140927271754 +12.4069472911079,5.18377504110927,47.285635272076 +12.3838008065961,4.74687430380634,46.4198616600691 +12.3890485226671,4.69290185618914,47.0008863441505 +12.438413019001,4.64737848855802,47.1474035447134 +12.4707142251089,4.74559617407603,46.5322759154382 +12.5061741295363,4.83235103349276,46.9228505222354 +12.5494062795436,4.95595708308687,47.4393067283071 +12.5540834581865,5.0121202468956,49.5296864383432 +12.5291178409882,4.91576923808679,48.0737767866368 +12.739474586099,4.91281480633089,47.5958913502518 +12.7443034642304,4.9399080387663,49.4810313496798 +12.7451985603218,5.01044293781832,47.896041147602 +12.7536416074642,5.11735073747844,46.9183858431142 +12.7415613914316,5.25800281556204,47.8897039921547 +12.7180951795126,5.41196775626032,47.8474818707197 +12.6113669500915,5.51056163019144,49.3495142870802 +12.6746616415725,5.52809774015331,49.054503736989 +12.1542237040698,5.13288955797794,48.3854904496619 +11.9268070043124,5.24787758196782,45.2964267945663 +12.0442902206316,5.16820315892366,46.3062739546352 +11.4821555610729,4.9471589305304,46.5418173625952 +16.3786472952652,6.61137817907523,46.409812184704 +16.3798050126198,6.59271093674357,45.3816565885124 +16.4224602751615,6.77246622925165,46.375933755574 +16.4033329786379,6.68244835432984,46.827668306331 +16.3610299911808,6.89564687514062,45.1108371086558 +16.3983468700271,6.85479141460962,46.2197292770533 +11.4103617154845,4.92600962348485,47.3206418445055 +11.3344431899816,4.93499146291377,48.0571344789977 +11.1455677131302,4.89791672775321,48.5190006617232 +11.0706162292056,4.89005291899713,47.7033177492813 +11.2420272034557,4.88940869475274,47.931763676377 +11.0505790116874,4.92330138446753,46.1733281103202 +11.4592796271283,5.24480624354485,47.3922916329354 +11.4427964940573,5.31829082556053,48.3523824146591 +11.3471735545276,5.36624875176502,48.2446337626745 +11.3287441795498,5.31270725256997,47.0044134683734 +11.4639460392368,5.31956339913242,45.2732960271362 +11.0004502712834,5.29229171025692,45.4331565285315 +11.0154909342975,5.3670648727668,45.0308064540335 +15.5306627923679,6.1290240030595,-46.8743338323124 +15.5507366822409,6.14913418119846,-45.31022371408 +15.4514933005266,6.18484998494129,-48.3797345595987 +11.0267988463175,5.39521717005474,45.4035420777745 +11.0896665724066,5.42347437577941,45.6360914910002 +11.0910840854549,5.42117989226241,46.0701115562793 +11.0868128216087,5.41307142346268,46.128567753674 +11.0976694781661,5.35619088690533,45.3076015882271 +11.4422216842372,5.50203949697204,-46.2663130781623 +11.2871499168933,5.57936275254639,-48.211291819709 +11.0255887118852,5.78404536551485,-47.8576175319102 +11.1570666633883,5.67457803726926,-47.9943306260345 +11.0733531478423,5.71599189093294,-48.2123401916896 +10.9453814587819,6.0630070549545,-47.6572198599259 +10.9875713969402,5.91698826119716,-48.2822403726802 +10.8862329429042,6.12126587906531,-48.6325452359766 +10.8749061375058,6.21473001999097,-48.8955983837881 +10.882695595174,6.20124382165015,-49.6299009946444 +10.8695308865145,6.18508926699649,-48.5976370006846 +10.9590398302682,6.22042990290547,-49.7499178430059 +10.9919316445167,6.31311968517777,-48.6171757361985 +11.0509402409414,6.45750006634719,-46.3135937995875 +10.9239378904713,6.19467343319562,-49.7764267996654 +11.1106137662021,6.543499906849,-46.831309151252 +11.1644965411945,6.59019910689207,-46.4192944221912 +11.1855640257192,6.54922879289917,-47.2930772292482 +11.2179986562201,6.37512046423867,-48.8504993147785 +11.2057043517572,6.43331955935039,-47.7850266676348 +11.2210274489037,6.24629606261011,-49.9359227564059 +11.2222741306532,5.9463578949304,-47.9964276107705 +11.213492975927,6.00037405768383,-46.446215988551 +11.2311052258373,5.98858098723968,-49.2497203780797 +14.7852504318927,7.89095127588356,-48.7771396213737 +14.7353257299759,7.8946009128346,-46.2930827363073 +11.1504105566835,6.02956365068319,-46.0132658457524 +11.0950473334492,6.09325031772234,-45.8618620605791 +11.0225106752103,6.08970411407012,-46.457980933509 +10.950383586208,6.11968597207626,-47.7364558861205 +11.1856784349474,6.00295272074576,-45.5858534050775 +10.8763226182203,6.14278176321005,-48.4596175353682 +10.7923315010191,6.18423995478889,-48.9984290825547 +10.6306169920012,6.21634851841337,-49.1678396297888 +10.606693608347,6.1356542435346,-49.5874171823699 +10.5245830556525,5.99602173666341,-49.9075199317861 +10.4411539192307,6.02763597180385,-48.3119193000601 +10.4196944969206,6.0479558405199,-47.858713031876 +10.4084424540128,6.06740957541453,-49.4245109458802 +10.4748672410564,6.03169383454932,-49.5203257887756 +10.3693785528126,6.11088673320474,-49.1413254334991 +10.342969839915,6.15106273678499,-49.3147003121807 +10.2824189679355,6.06681051873058,-49.375429413378 +10.2635232692362,6.08450279300773,-49.0452523165254 +10.2437478602291,6.04664936167123,-47.0050718942006 +10.2251378161832,6.00295727617718,-46.6168486124888 +10.2059089500593,5.97196157601709,-46.6647731248982 +10.1690839269455,5.97174446992218,-46.5787510114171 +10.1097660353146,5.88351910067622,-46.561102925551 +10.1308524269303,5.98985761734242,-47.4270818731936 +10.0721683949379,6.00153590413349,-47.0820676509178 +10.0300091476072,6.02143579528201,-47.0953296417416 +10.0975331358462,5.94888781249675,-45.7924579698806 +9.98640934093249,6.16818065881496,-47.0073369442267 +9.97761063476635,6.37815124840152,-48.9765883459226 +9.98352911660971,6.19785821409253,-48.4453579987416 +9.97649429970807,6.37629426596395,-49.539783326573 +9.97783145489929,6.45201269940221,-49.6454394439651 +9.97951247347303,6.54881162939794,-48.1223722006133 +9.9590222192425,6.68492880675701,-49.3396281285539 +9.9807084266298,6.56401896396575,-49.1450438010065 +9.87400479864628,6.80855338353682,-48.8357310773671 +9.92989942115524,6.72621049410386,-49.6035773967042 +9.83345035875006,6.90778162711137,-47.8437414763274 +9.73500996795,7.08259738547494,-47.3051698372537 +9.79792452846309,6.94093279534098,-47.2486791012995 +9.61240490261019,7.26311155540059,-47.6166330489971 +9.55039669873128,7.33709979179921,-48.3441662539865 +9.46600110822388,7.31833425262501,-47.2951387505916 +9.44526085705379,7.24485948014193,-46.909700446128 +9.51868551703511,7.39033578768866,-48.1821395096123 +9.49951655823229,7.40219392875526,-47.8948762161912 +9.41778523306979,7.16249185759334,-46.0126659725606 +9.38909174534475,7.06134466898695,-45.1457550616236 +11.4510112228966,6.19525363630305,-45.9252725692265 +11.4564931382269,6.31555914543671,-46.2960772000584 +11.4602052912483,6.47192839033009,-45.2977039898885 +11.4256862547244,6.51496568916934,-45.1637657319191 +9.3736420486644,6.59809922548999,45.5800001126157 +9.4358050453574,6.6179241541826,45.1830365329473 +13.9222622196076,7.54939343896664,-45.1246969717079 +13.8961525511278,7.55125387463547,-45.8147883455831 +13.8269323053157,7.4699628172492,-45.6693631694376 +13.7479390099058,7.37871085632053,-46.3148827643703 +13.6568356964245,7.13462696266498,-47.9820527441102 +13.5515739756679,7.08461325212769,-48.8936262907054 +13.3883662349023,7.24680821152962,-49.8916212319978 +13.4583135608441,6.92777105308556,-49.9865413262987 +13.4071983131082,7.43771493648797,-46.9239504206907 +14.5563496585721,4.8435694026493,-45.384250594279 +14.4879908898154,4.8160986240345,-45.6732385239333 +14.4294003648701,4.88890135161052,-46.5954698155823 +14.4746372041978,5.06959101980941,-45.815098794315 +14.4292270626333,4.99211606395466,-46.5874017309399 +14.510403465608,5.0653438532899,-45.8964053437223 +14.6262608370212,5.25632558571277,-45.6696481720844 +14.1080534165011,6.54907996163232,-46.167195205713 +14.0273444400856,6.44556292862649,-48.6929957850218 +14.5751733995371,5.26647588580217,-47.0897880922137 +14.5156969334415,5.24553930213034,-48.9828344585522 +14.4967043841626,5.20721623654303,-49.9839284037068 +13.7326903442996,6.98391893261062,-49.3575757252207 +13.7139403443119,7.21677538020315,-47.6321830250959 +13.6823007695844,7.61759491105723,-46.5547712816845 +13.7017010080537,7.39055081261724,-47.0998923397183 +13.3376147177574,7.38198729306837,-48.2170380065672 +14.4306579834849,5.12609340556243,-49.9330438214692 +14.3223038376119,5.31600690454401,-49.4169169927014 +14.3154807385861,5.38680432674908,-46.9350940525883 +14.4301101667152,5.19929547035644,-49.7770335031043 +14.2941885432145,5.36636164463871,-48.8170339565632 +14.3407381196262,5.40009853792594,-46.3650062006044 +14.3535514342443,5.31652322597601,-46.6192277940956 +14.3740973555562,5.29233186274444,-45.4808282550793 +14.4110985648678,5.24923095831973,-47.5329635870182 +14.4082344577367,5.32594435548782,-47.7108526410174 +14.3869810237146,5.42176384873532,-47.9450700638165 +14.3816279151567,5.55591720926067,-48.9678475981173 +14.4046968833949,5.60475467794343,-49.4651077111826 +14.3966480707888,5.53769761640826,-48.2555589131996 +13.0676692108437,7.61469032971001,-45.6183813964367 +13.1467986341458,7.54505673426112,-47.3878008249266 +13.2090060133285,7.49563751449594,-48.4632747631078 +13.4195982986583,6.41879991270626,46.989943381514 +13.3351698502819,6.35783051771468,48.399977786641 +13.2645036594523,6.2555714113916,47.654883619742 +13.4820486095133,6.54732164619579,46.6731100810351 +13.1773069761155,6.26950116203522,46.1193879195476 +13.1497425908346,6.27489451582937,45.1074368269244 +13.5117533631616,6.59429942345395,45.6203696624854 +13.2110426929148,6.22856629069778,47.3882849797066 +14.2752019896424,5.80387041095629,-48.147511608638 +14.3032475176343,5.8282807896139,-46.9149033322791 +14.3031891654051,5.9596950882444,-45.2128731208209 +13.9853452222109,6.27545236751069,-46.1581327024516 +14.0367835672587,6.20481608187577,-45.9976369511126 +13.966608013079,6.37136780190654,-46.8385349483929 +13.9212689786651,6.44348378908142,-46.0841881141513 +13.3932445213853,7.38424665514543,-49.8269260864646 +13.362247522509,7.37970417114813,-47.7828762663938 +13.3594961462424,7.35083136503023,-49.0058467369485 +13.6493661688793,5.98170739999911,-46.4514955008077 +13.6673072146584,6.02324620312186,-48.0819567544272 +13.7042727117324,6.07660605375016,-46.4738919336868 +13.7498200225007,6.46265147740765,-47.6488742880853 +13.7170232980426,6.66993325068613,-47.5884340486585 +13.7697147987324,6.36480909610199,-47.87421927471 +13.7474441065074,6.2482074416978,-47.8308339525902 +13.6926855282912,6.77729919542239,-49.808338418029 +13.6154310539069,6.93317194075731,-47.9759339754171 +13.6509639917274,6.83516457167564,-49.8513091671699 +13.5604354712998,6.97045872881998,-49.5452426939501 +13.5194267929375,7.04248505484234,-48.4488606540135 +13.4471695350114,7.08741964863715,-49.8062255845832 +13.3711755711341,7.06598359353447,-49.5115856435551 +13.2795509038891,7.01367422493353,-47.8652990477508 +13.199514659846,6.82932638947621,-48.788049222148 +13.1294093682179,6.72290322278844,-47.3590564237788 +13.0621113747767,6.61555056523138,-47.064519101108 +13.0014145849157,6.41802094893432,-47.586963688683 +12.9370507940346,6.25893765871003,-46.7780083333564 +12.8493366957692,6.13208164455338,-47.1654799644764 +12.7940855058467,6.07116272915983,-47.8679197443331 +12.7211863476456,6.16281704387038,-49.4615466115102 +12.8080384364379,6.38478895954231,-48.9549213329189 +12.7556677328091,6.27935901105232,-49.3824818978302 +12.8496127249412,6.38867442364361,-47.9126360999869 +12.8650661234457,6.44806597963317,-49.970836004304 +14.6666076998268,6.14928978628833,47.6361365412804 +14.5911469709519,6.16317604766526,47.0054538163521 +14.5239044510753,6.20254714054079,46.9502494730783 +14.4490817031352,6.19117990684786,47.4493339866872 +14.368497958924,6.23013573173339,49.1285900794628 +14.2999955649466,6.35681622866272,48.7892019215976 +13.9106207144246,6.58925011041698,49.6690065394002 +14.0668032808442,6.51427826073636,49.703856905423 +13.6798473177951,6.65206676492575,49.9460451065407 +14.8178360316737,7.32147849194141,47.7926623434832 +14.8230012121299,7.24921785170853,49.9977207207933 +14.7930040931889,7.44929857352391,45.5841258675592 +14.7580023411651,7.47078888911643,45.8977743217503 +14.4584069556902,6.64339773307223,45.4625083427923 +14.4087018517505,6.52195429075206,47.370346243535 +14.4334439127969,6.58485063368502,46.5677086334269 +14.3194403728744,6.51727101234457,48.9689122375209 +14.284919243095,6.44989945222366,48.4821834545416 +14.359766959692,6.52634818395524,48.5071746770866 +14.2049731749,6.28131606399556,47.5761203157991 +14.2640437532176,6.30545984136887,48.3778624292057 +14.1664280601521,6.30214037447241,46.5850175550748 +13.812807739162,6.87755963362694,46.3427389195768 +13.6772645092715,6.39516304215346,49.4784771509671 +13.6667423368021,6.33832292257421,48.4454992503427 +13.6418883280377,6.36579792919337,48.5118888910218 +13.6386578576277,6.45070035909264,48.150797617056 +13.700273479518,6.51679614160518,49.4203502367022 +13.8029531342416,6.52384775111068,49.8303060681816 +13.8787493596163,6.56433946267543,49.1112165099339 +13.9576984625807,6.58684789782299,48.2547206724022 +14.048942949882,6.62300769472316,47.8417480913276 +14.1366065048291,6.65852836455469,48.2063779697667 +13.7725101921242,6.79108645929757,48.0645985746872 +12.4124231084515,7.52961173753213,48.6915565635637 +12.4589908570509,7.6073412383652,47.5767970215726 +12.5174017899884,7.65329760629671,48.1787358038551 +12.4860464262129,7.6413132028235,49.5987178733259 +12.4961920628326,7.55962330265792,49.1232259548325 +12.5407424988663,7.40458736588668,48.425167354759 +12.5293823455945,7.34246022826524,47.398211207744 +12.4867171340592,7.30623140131394,47.1848822297861 +12.4565377511496,7.22737273893591,47.1747944534103 +12.4282353024172,7.19383196167691,46.9302529204192 +12.3825964271862,7.21931828037792,46.4674816067545 +12.3579246902196,7.19804216601263,47.3629138624312 +12.2642923967751,7.61693075405071,48.5112643792041 +14.2869196754902,6.74235075274354,46.0714096192049 +14.3761998246024,6.68806435739974,45.185045185294 +14.4040467633737,6.56801930245277,45.2379594327769 +14.4425906438904,6.51294377439177,45.3312897737321 +14.497889518603,6.53401770827805,46.9633924963856 +14.2178281814524,6.67366858273402,47.4743026742265 +14.5133620231281,6.48405783984425,48.8336568831717 +14.5429284148293,6.48053395443808,48.5827401210363 +14.5112897063672,6.48715308518579,48.8077040147946 +14.4473995632122,6.52586425841403,47.0940151278848 +14.5430823428259,6.45429277316842,47.6175724804643 +14.368170699764,6.57092882767663,46.9704878285505 +14.3185590817019,6.63917207551258,47.4999611431744 +14.328123163159,6.70877541069899,45.1416606901165 +14.2645479291337,6.64189040140646,49.9979348487546 +14.1873439070631,6.71338639281609,48.3459189291932 +14.1574308275799,6.73810871533317,48.6114160197128 +14.1260117196118,6.77241518844609,47.442424426823 +14.1107892753699,6.82213034385859,47.6320983220435 +14.1184306999967,6.9018552068462,47.2557233695163 +14.1429329541574,6.91319454363558,48.3037815473612 +14.1487506170743,6.90714579028672,47.2842509690728 +14.1280586133553,6.90416112893677,48.5339326030626 +14.1109157457041,6.91575396312018,49.114517307153 +14.0888174303129,6.98675438833388,49.6384905688274 +13.9474297317256,7.23000270728383,49.8750701701228 +13.9756835832209,7.36346972163819,49.1961114899964 +14.0083114152219,7.44015007287053,48.5084249974755 +14.0651572672716,7.55263149527704,47.9298170128323 +14.1512547020282,7.63229785887641,46.6957483884353 +12.1947578352028,7.45510747329864,48.2660907518379 +12.2310001587978,7.46060121293032,49.7181127894178 +12.2315596578316,7.54199482563925,48.4300041561594 +12.1940594303543,7.4459668636214,48.333295002124 +14.2226073276821,7.63309436757902,45.3968913411953 +12.7152402357452,6.5320617219877,49.1936089230953 +12.893239374357,6.4364622776608,49.8348961456168 +12.7604600712123,6.49313603695576,49.7586260589327 +12.6311609907958,6.64219972985059,49.6872611655339 +12.6796756644598,6.5575105984676,49.067926937655 +12.1383347077297,6.87458506969534,48.9298233440001 +12.1108305863121,6.87005067772125,47.216803603926 +12.0590736275709,6.84355500118077,46.9961355046466 +12.0181526964235,6.85154288648843,46.7301248893756 +15.0023285844376,7.19295565105574,45.2633102250329 +15.0456753262341,7.11251079841025,45.2784318906356 +15.1081513950545,7.03890459810669,45.5693555840198 +15.2163458610866,6.95048262125331,45.9913352595685 +15.3146139743983,6.87364761572171,45.8776082138703 +15.397084816282,6.92067395167769,47.0187209835777 +15.483041382539,6.95116698823988,47.7415529108183 +15.5429074699327,7.0422308255482,49.1003103897769 +15.4242956023438,6.90570607145535,48.9690266169521 +15.3906051572236,6.94942619677695,48.2316059323238 +15.3265172969527,6.9798243608204,47.5496334849084 +15.2543469550601,7.01255073431272,46.6401803803723 +11.9856023582832,6.84393742073768,48.2062413932649 +11.9541219103613,6.8053477527447,49.2633083563236 +11.5760775859441,6.92334051409329,49.7144011246643 +11.5140573500042,6.98520498762946,49.8632817976909 +11.4888007096459,6.96080965115904,49.2926736969665 +14.43817747947,6.58056660856315,47.8201074549115 +11.438273628638,6.9249036672193,47.6527774457426 +11.3958451539703,6.90461462655695,49.3316951563045 +11.3637523038251,6.9306263710368,49.4494360969478 +11.333832566731,6.91850161252734,49.1598387464431 +11.3491071571851,6.95342291001337,48.5816923659879 +11.4120308643748,7.07115138930213,49.6874147703826 +11.4506481859413,7.17529044068266,49.7240438558824 +11.4949644620548,7.20725068644279,49.6936799671311 +11.5765392535927,7.20112593891931,49.5017393415595 +11.6494198527872,7.17266560186379,48.5725865022166 +11.7088085184913,7.10754820421553,49.7637971563378 +11.7631635798548,7.06786527731087,49.0755519596691 +11.8139381515581,6.98972444895098,48.8136410853452 +11.8574592096129,6.89017243836174,48.0425264472169 +11.9232087614062,6.84413960583298,47.5055966090628 +12.0028220824065,6.78099178394084,47.616722807044 +12.7514977239398,6.81527080805262,49.8376166438114 +12.7041917895633,6.83121143538274,48.4475365310149 +12.6878410038744,6.81996796690886,47.9520563168119 +12.6903384040556,6.72744403208056,48.2080231836437 +12.7003966268673,6.6033238641423,48.7796290462567 +14.6814779721209,5.22621434401849,-45.6482936297194 +14.7744546760303,5.2765730440697,-48.1435437616267 +15.4610033168201,5.99829808640704,-49.873336015786 +15.5241462636945,6.04579543932536,-48.665591183312 +15.6350666635533,6.14333054385063,-47.6255372217086 +15.7311170670718,6.29493193718841,-47.299774566401 +15.8276640286172,6.44324324420738,-47.0767133920476 +15.955675749997,6.55998633384886,-46.7651072329975 +16.2338361043331,6.49301606640323,-47.18889644828 +16.0914178907292,6.52067683897567,-46.6789302661605 +16.3640748030114,6.36027521009925,-46.0155647476627 +16.3693505246867,6.3139397369587,-45.3895678585543 +16.3344926387468,6.48979923657158,-46.4302672594981 +16.4300395683077,6.26263954013946,-46.235558391629 +16.3921790491574,6.25558163783447,-45.6642498572975 +16.4456996975564,6.33085162484174,-47.1604178273065 +16.4962208860611,6.30815142076578,-47.8167329855275 +16.5923180384834,6.41078423679353,-47.8685161560335 +16.7225536547082,6.4391307548642,-47.7575281849222 +16.8275493028032,6.44849368121332,-46.609747346944 +16.8807695560343,6.51451730890503,-45.8129266162066 +12.7040638183911,6.45486187931404,49.4329942387462 +13.7312691739579,7.54875087980387,49.9157360192267 +16.0785732803701,6.45553491496314,-45.7842611157558 +15.9985954946947,6.37692901315352,-46.4912291471387 +15.9122848642673,6.3836989831989,-46.7014255196853 +15.619768015807,6.21439385187069,-45.404649175769 +15.7957528899106,6.24672864329092,-46.3294931319604 +14.3912072069465,7.65344452232423,48.4235160705107 +14.4557573794725,7.6171847648531,46.5102175149776 +15.4705694239945,6.13410127914652,-45.2369377439955 +14.9420529927307,6.1848874187184,-45.5163641946205 +13.9795203010051,7.10310665403583,45.9718862101942 +13.6109557355025,7.11341288485763,46.5102899671539 +13.7290063314943,7.0663020406086,47.0521238906546 +13.5133508026687,7.23990511788024,46.750283687282 +13.4636493640679,7.36264875934352,46.3854770099137 +13.4488047025814,7.35959542293252,46.8121605709967 +13.4162844031579,7.42141023888477,46.4555181669969 +13.8624238766231,7.08615632826151,46.2786928596036 +14.7356619595751,6.37893347643279,-45.3501742646268 +14.6835935440423,6.46516960326452,-46.1089932835238 +14.6452065937843,6.51673780177896,-46.6074533345366 +14.6037113822655,6.49390829042138,-46.7800090161895 +14.5452004046982,6.41263201819159,-46.9432344675712 +14.4116432253485,6.24471551027664,-45.2502183746467 +14.4881611583879,6.39270502233944,-46.2022953798692 +13.4000537630376,7.46572908142789,47.1526655009227 +13.4425104612288,7.55527079049591,48.3445865506235 +13.5497333358851,7.60355683489873,48.9344166128004 +13.6322205249853,7.65720295995292,49.7122599582585 +13.3223442158737,6.54255104533197,-47.3361577907777 +13.2608073892474,6.57072681923572,-49.9144116272589 +13.9390263643706,7.48424676317211,49.0002872999144 +14.000385567274,7.39502202632878,48.653197094434 +14.3397950253796,7.38551836651879,48.4362746639057 +14.3967756698549,7.22017602488506,49.4155152481308 +14.367684217358,7.35587639542374,48.5240449907008 +13.3722768079369,6.6334298984509,-48.3555581896815 +13.3399166814683,6.6504026760772,-48.8694130363956 +13.3762560390241,6.58406227564526,-46.6472577879996 +13.3268917617335,6.38178158168563,-45.1339587044714 +13.2797330607335,6.32280737201287,-45.752405446202 +10.7698463122086,6.3196902759211,48.9506132823831 +10.8168611996953,6.4329218747133,49.7402467120373 +11.0210775113275,6.60920767994914,48.623372081593 +11.1314294688592,6.57658871985867,46.7310054780781 +11.4975088729718,6.54713466337742,46.1611017133195 +11.3641583165591,6.55355562351034,46.6187566284155 +11.2312149958978,6.59287523394921,46.673983019244 +13.9043032474037,5.27801813558139,-45.6542879311725 +13.9050110251505,5.30366235660873,-47.5731472122219 +13.7970222071667,5.70018032107425,-49.8456557322158 +13.8131920687481,5.98179471583363,-45.7605298257578 +13.7996131435926,6.01477187985534,-45.2213451967363 +13.8369677654105,6.07313087184276,-45.6054078720767 +13.9129395864809,6.13281095908853,-46.5928211941486 +13.924022928822,6.18829731349598,-45.4099078415672 +13.8876146830962,6.19232861747033,-46.6627481681311 +13.888681708911,6.19556041886418,-46.7285310251597 +13.8851083098987,6.17011212360249,-47.1615707595441 +13.9186577278141,6.13120786003532,-47.4761869545625 +14.0064446673576,6.13902276305787,-49.6943777655727 +13.9867540363459,6.16784777325248,-47.6168193004205 +15.0921889076831,5.9801220729215,-46.1098363358701 +15.3615971819615,7.25715149344569,-49.2016311792212 +15.2929924405019,7.31199313951731,-49.7709074216131 +15.4172560617686,7.1397454736815,-48.0151943295488 +15.4644120035197,7.03098437377375,-46.785015812135 +15.4477955221033,7.00430898477967,-48.2864725843936 +15.4677628539107,6.98010899429514,-46.1507300519843 +15.3825025543842,7.04810692458299,-49.2393947081838 +15.302629880713,6.95357953153808,-47.9943098443735 +15.2562741775786,6.87168900160274,-48.2155839896302 +15.1936295361179,6.78283389803718,-47.8470241883305 +15.2067140557066,6.73670992172736,-47.5919933835311 +15.2309606077567,6.63022178159641,-47.3622228492592 +15.2494618397025,6.68705922490284,-47.6045065454701 +15.2223174850384,6.66776255914497,-48.2043971176889 +15.1683342458143,6.71514679560372,-49.9569111010631 +15.0976894281287,6.76756359102277,-48.5664615845051 +15.0533598239863,6.77640297103548,-48.3674077038844 +14.9433308920808,6.8802824482282,-48.4074156666778 +14.7683633380232,6.88389746164167,-47.5067070975497 +14.8613815736049,6.88335220828709,-47.6203526760629 +14.6438706792308,6.59943698146137,-47.8876433227103 +14.6298040148278,6.48220515022242,-48.9644041095438 +14.7116972507104,6.41211690505828,-49.610008492195 +14.7265284679476,6.38415056583326,-49.0360834400921 +14.6890724472072,6.71660148095881,-47.0036504230998 +14.7057468654448,6.46250437900501,-47.6070326262005 +14.6648459876493,6.34689840722588,-46.415722660284 +14.6816772337477,6.47546527764785,-46.9349580217757 +14.6381224330598,6.37580782207727,-46.4961704474353 +14.7021625727802,6.85860521986458,-47.0041617276791 +14.5794582142877,6.3379525251346,-47.5054976687011 +14.5128694297978,6.32456511292779,-47.2457204682669 +14.6202196405998,6.36709034074444,-48.8785617560396 +14.3188388578735,6.36910610624776,-45.0069836054669 +14.417686720231,6.29683541779257,-46.724181016059 +14.1179886839802,6.27159117891668,-46.9091561938586 +14.0155712102085,6.3229518324037,-48.1992545849526 +13.3925622543728,8.15836548190019,47.03636372509 +13.3357083154376,8.35196830352247,47.7095566684091 +13.3063500419327,8.61419935517757,47.7664291744127 +13.3097834489526,8.81056992990161,48.289091867431 +13.4336419115648,8.72785189555392,45.7608307943693 +13.3660050813166,8.86124068181831,48.161283638024 +13.4660395920436,8.44446130582552,45.1494308326142 +13.4270181784783,8.01209378123096,45.7848961602855 +12.3091004490629,6.33139941150757,-46.2511913813491 +12.175137712138,6.5971969542929,-48.0654168868242 +12.2065644769286,6.48143056525446,-48.7205123755455 +12.1807730376246,6.72346693612847,-47.9545139148015 +12.1801894558557,6.67777291450555,-47.3892177038005 +12.2041718791181,6.74660315198517,-49.2313243073935 +12.4269252466352,6.43619748095226,-47.3221531987429 +12.3602850308164,6.51650846431223,-47.1425272163511 +12.5244614236146,6.31278081582026,-47.3477249614432 +12.6072398055195,6.2534234330966,-47.0400510316596 +12.6670931160807,6.19876602778175,-48.5841499886476 +12.645094210024,6.18349823525984,-46.9981135861139 +12.5597838621032,6.37129691724903,-48.2593850053806 +14.3763601577354,7.33807173275052,-47.2527954421992 +14.3592793402471,7.42211474453793,-47.3964663499898 +14.3242180761703,7.551062614874,-48.7580323332176 +14.342197800585,7.47712906162842,-47.6365296588504 +14.3922252499137,7.25615559553018,-46.2726113342478 +14.3044560864926,7.3069974590843,-49.2033321042405 +14.0853209840493,7.42188013936929,-49.3099228383482 +13.9326383830807,7.66768303789115,-49.2942741302373 +14.2069773516109,7.3267870544642,-49.7916751774428 +15.4200453914585,9.24090443660283,-48.6206364612979 +15.4471058598931,9.08428687395125,-47.5357436021142 +15.4899558491181,8.85077634033564,-45.9547978330954 +15.5161374310221,8.77003638914623,-46.1573387444307 +15.5369212391085,8.67399953718568,-46.7051225327692 +15.5534098845653,8.65735350052902,-47.0249663402187 +15.5415963135208,8.68561693447187,-48.5875277006417 +15.5193531719151,8.65247867793791,-48.7034508330658 +15.4845447096748,8.7283690967637,-49.0922562992913 +15.4412276426713,8.77581205713893,-49.2846969060727 +15.2782460722106,8.74435603931786,-49.2418791800353 +15.3972051310201,8.84609731503528,-49.4967338121895 +13.7115222136758,8.92037110578472,-48.0413989788128 +15.1987161721366,8.88861004447705,-48.9101882835587 +15.1009217255295,8.90921825293959,-48.8596218452433 +13.7300856154,8.98454826012065,-47.9253570811168 +13.5949279901025,9.03786431265472,-46.8764508647612 +13.8119846840179,9.03302933855083,-46.5058957278861 +13.8605007808406,8.85462635504871,-45.5909130729422 +14.1339344383426,8.31905978917964,-46.5519156343359 +14.0399568222282,8.43240975651349,-45.158363823526 +14.4825617322883,8.13433774329526,-49.5323045292896 +15.0713781638836,8.08143035051539,-49.5249400391217 +15.2806354457156,7.894613179254,-48.8303221268537 +14.2897762669306,8.27978525568812,-48.7609774234275 +15.1800597488257,7.9906740520806,-48.8331518757919 +15.4212208582027,8.10460671017456,-49.8211240541655 +15.3523480731378,7.97082163614432,-49.5109304889106 +15.4752885321777,8.2702529642857,-49.7640011192264 +15.879827412974,8.13645819504517,-48.3965115676468 +15.8652642346913,7.98783773772975,-45.9928268233301 +16.3159495257617,9.20927785836685,-47.9330030152199 +15.8142135039388,9.07467994811289,-48.8955697180031 +15.8787276824521,9.02536728802371,-49.3984379107852 +15.6850572427908,9.48510629388892,-48.938672641389 +15.749472264738,9.33223695584911,-48.2272142459712 +15.3854095617458,7.69879944977773,-49.4167415894894 +15.4356975057402,7.7503390090145,-48.2343531423169 +15.4377204182906,7.73856352625963,-49.3907582504499 +15.3940876739893,7.59626494391973,-49.8998362351313 +15.4066739183397,7.77205189543496,-48.4756926512666 +15.6321440872201,9.64116805388963,-48.0135750594265 +15.5966612567262,9.80470367779478,-46.7864209075971 +15.6174142812055,9.71139409253762,-46.0441388920068 +15.6023089961007,9.7875775509771,-47.058483751757 +15.4946949216391,9.20882164124807,-48.8055922604388 +15.4782376369823,9.06327719236234,-49.6419793749518 +15.4989528344498,9.27989923847019,-47.6392045945662 +15.5042283094837,9.32191239989828,-45.9002125823056 +16.301686963621,8.0758371960308,-49.3221540728954 +16.3341317788063,8.23314201977706,-48.630883794262 +16.3441848470426,8.18717662298654,-48.6994841221041 +16.2850313042933,8.21528913549645,-49.0598915571362 +16.3285306793644,8.25453412415199,-48.790183474176 +16.0688141966025,8.10005623061463,-49.8268312006689 +16.2396035973753,8.12667626387322,-49.8616353577984 +16.1859390906756,8.01201630080008,-49.9609750492054 +16.359738377878,7.98154410466623,-49.8184587257148 +16.4188047333801,7.92629558906705,-49.7912699017032 +16.1617421223762,8.10304954119732,-49.0744401008377 +16.040666848979,8.12538954373815,-47.4215944676388 +15.8307460379146,8.22834018784446,-46.5828746297874 +15.9165062490698,8.20752543781247,-46.9588842690288 +16.1140700312765,8.34056629078932,-47.7346852582375 +15.8517952411668,8.28928939261368,-46.1392153712616 +15.9549533850024,8.3567698168817,-46.4002343485988 +13.8400585494539,9.12918831730284,-49.02620307967 +14.1145536564257,8.84273578109206,-47.9834719151449 +14.2468560811218,8.74493587881376,-48.5983415123675 +14.3416654131489,8.5548005803069,-48.9546372971207 +14.4195769222782,8.60095156731942,-48.8707325864088 +13.9967354951757,9.07761319205794,-48.5663862917937 +16.8688325771046,8.29948646040514,-49.437541483743 +17.0351576832703,7.9675879210166,-48.5446075845436 +16.943062178799,8.1308780370796,-48.7237818151717 +12.1564966484653,6.0445791796379,-45.1854095276282 +12.1425593206552,5.96877517514864,-45.0617072271176 +12.1455891778989,6.14055632098934,-47.2012707029851 +12.1687400973078,6.18665006027046,-45.7931223554502 +12.1203644719129,6.14121642339764,-48.9760817992558 +12.5750623934975,6.62606029411228,-48.9892504571873 +12.5575984381795,6.61946656897848,-48.0887339809134 +12.5808662963332,6.61685724439051,-46.5574489196809 +12.5911776148946,6.56716461588447,-46.2027608033237 +12.6112015160445,6.50428873574536,-46.6448849936627 +12.5922183431513,6.50255789061417,-46.2072315987064 +12.6305801939906,6.51709925365655,-48.9024668264736 +12.6780010897855,6.5999491505717,-49.5108359061173 +12.7348415637802,6.61760642247997,-49.638367628573 +17.2012582057911,7.47142317066787,-48.3493075629558 +17.1187571039939,7.72952758242802,-48.65947038454 +17.2288050396466,7.27802672488399,-49.3729804037587 +17.1608733929918,7.04999535330839,-49.0314095071829 +17.2002884127599,6.9415891206676,-47.523699525847 +17.2582785233984,6.87948424075285,-46.4379825757454 +17.3083312541608,6.8183197375147,-46.2574469453337 +17.3108268112353,6.79402326493252,-47.5857400761664 +17.4219748043864,6.72532369922128,-47.833649501643 +17.5366477177871,6.77244398928299,-48.9012441980636 +17.6433881321875,6.75494808076924,-48.6732806774334 +17.2874703780535,6.82815479923659,-45.6188426132288 +17.8031620809169,6.82326318244789,-48.1978679615407 +17.8854654528815,6.94682390346433,-47.3173812425396 +17.9540532052209,7.05657994001649,-47.0326174874067 +12.8044590164069,6.57126375183402,-48.000556814362 +12.7793488571983,6.62709226393922,-48.8932989188195 +12.8023138049404,6.51417765920828,-46.7965215123239 +12.8621612004885,6.51129040481807,-45.0769778306656 +12.9870242351763,6.61409547457424,-45.7480951098403 +13.0506988307973,6.67859160439677,-47.5865636157702 +13.0600667119278,6.68825883364218,-49.225035686032 +17.9664801803506,7.15146191343835,-46.6649040532424 +17.8144473444962,7.09092937526394,-49.4039672041178 +17.8896342221539,7.16428472546,-46.9352897633689 +14.5780025538648,8.94740560682886,-49.407644754834 +14.6828031473373,8.93792318837335,-48.3049580150383 +14.8500340442484,8.72673806613643,-49.2044096656724 +14.7224361142105,8.94752828433401,-47.819111327908 +14.7814870711123,8.89238852403943,-48.7764827688361 +12.8671960757096,6.5780767384515,-48.261027839017 +16.1355384970871,7.30725352591922,-49.3058669215826 +15.211024102725,7.84230969875468,-49.5760313659188 +15.3113917584911,7.60136525040956,-48.6444258118897 +15.4067400765282,7.52544184412952,-47.8704685385653 +15.5259479945002,7.60362708928582,-47.4664589734999 +15.6610418236691,8.04499403653165,-45.2490167947371 +15.7182435607426,8.08546169260279,-46.1209766553898 +15.765686085983,8.09773728181204,-47.0759491678232 +15.7815425698452,8.06021205573206,-47.7615026995145 +15.6014704078378,7.71217869404484,-45.3419813993044 +12.9880992781127,6.40933001340035,-47.4547603769361 +13.005394392887,6.32102071992473,-47.9465206454784 +12.9433855262843,6.51975136658617,-47.9131119174991 +12.957654558786,6.27050759811639,-48.10735060588 +12.9203188671145,6.39535171703751,-49.6907934668708 +12.922051855201,6.85962375896628,-49.7266415747109 +12.8425752149252,6.95876946977411,-49.5126353545832 +12.7010143718986,6.90643703633335,-48.6135246775803 +12.6329326749125,6.92457294432234,-48.8241173810658 +12.6699301823209,6.96763002002565,-48.0452164748209 +12.7734408355001,6.91610035069041,-49.4355215255622 +12.6480524908122,6.8209664367248,-48.1047925952962 +12.6980073821375,6.92129519703748,-48.1399682600169 +12.7016069739586,6.8252086638032,-48.0026272292267 +12.7050123854087,6.71341251082084,-47.4988463934908 +12.6812218402452,6.64007145773305,-47.0680928207778 +12.6661952177852,6.4989861219022,-45.5009715400884 +12.6176490868515,6.47703622903273,-45.3635608331178 +12.6040509309852,6.44986799573953,-46.5369240654831 +12.6457241685611,6.45262926662128,-45.598269834181 +15.8451882414491,7.91457988388469,-48.2090940275677 +15.7991909038046,7.89709178143952,-48.906574348293 +15.7250372242201,8.08145323701427,-49.118419637732 +15.6887712167568,8.19750075550589,-49.9042687570351 +15.8028520083535,7.94021302986705,-47.9255109189393 +15.8092264696154,7.9138391113735,-48.573800251618 +16.0470464467276,7.51324902777997,-49.0930160851595 +16.0186830655694,7.67117930399416,-49.1420413830811 +15.9405825808318,7.88056377218282,-49.9811796059467 +15.9172397903014,8.05039953995509,-49.5874738264826 +15.8862522619312,8.16318570501952,-49.398342556481 +16.086722330216,7.36652176899424,-48.9120921254155 +15.8525267081833,8.15010901494587,-49.3841698021583 +15.4762574202356,7.97382643468587,-48.4299682833419 +15.4964552017501,7.84702657373592,-49.4094720325644 +15.1020079111668,8.0537995809745,-48.7834445190902 +12.7950340150549,6.79920717093647,-46.228371664965 +12.793100385542,6.77223068248119,-49.8781535911683 +12.7420966801838,6.73235263647342,-49.4944502732585 +12.6829517948404,6.7311158813501,-48.2259343260942 +12.6213493085799,6.7251194200678,-47.6213586963131 +12.7535199470095,6.70671625522832,-45.1636994978443 +12.8170183339499,6.84468817056668,-48.4481009205856 +12.6043838315724,6.70296661805608,-46.52745770916 +12.5750753986591,6.77954308709574,-46.4012140243616 +12.5803653067399,6.76345764861165,-45.6354906665872 +12.6071546241384,6.70438726894353,-45.728802781312 +12.6058309194267,6.53734891092248,-48.6165010702149 +12.5996267587317,6.66163813169241,-47.6720908972812 +17.0614611650494,8.11498848083222,-49.2256039441558 +17.0778159702805,8.18342452067561,-49.2176029831983 +17.050926972781,8.00626080231365,-48.0667196875573 +12.5856018918821,6.44446162843163,-49.7540351077159 +12.2987267342159,7.10458597472325,-49.5685828984179 +12.3121003704438,7.20719266276599,-49.2366227441324 +12.3624900462982,7.03257759131104,-49.8842842546292 +12.3332710317425,7.15865942244077,-48.08840214717 +15.0582996621133,8.7972551058149,-48.891554345093 +15.0232953652019,8.75633027870776,-48.8096838343021 +14.9555179993571,8.76907028645907,-48.4299447858193 +14.8313837011236,8.42179228188722,-48.8104749506305 +14.8723861104569,8.62641842366815,-48.5879370999549 +14.735877224533,8.0933316264955,-49.6286531924273 +14.7829085051655,8.22629218447929,-48.6518838093385 +14.6867475785645,8.07748854338645,-49.4289987916616 +14.6712138400882,8.18668253918812,-49.1816338328558 +14.6926127609011,8.05885487280255,-49.3885145982285 +12.3919113143708,6.84040332011865,-49.823942243172 +12.4292194535265,6.70499333618146,-49.8133454670642 +12.4373991662434,6.63030631659374,-49.4405228112113 +12.4240379512692,6.61781600515128,-47.386139552074 +12.4412996662105,6.6045054070296,-48.662827790679 +12.3751477829507,6.63661496433506,-45.9098766014802 +12.38703802732,6.6168310512234,-45.7895589421568 +12.3484927896833,6.660093838678,-45.9973476977335 +12.3193418985337,6.63394148277764,-46.6256072980498 +12.2924130487302,6.54339914961923,-47.9023975149745 +17.0255101006026,7.84402512980144,-46.419729250989 +17.6319157134302,7.5037264679839,-48.0235428030524 +17.4864763079053,7.48834493572653,-46.6337871744531 +17.8918924919781,7.65482072746627,-47.0730153917932 +17.7582349068599,7.54757055724305,-49.7984144650934 +17.8695911641202,7.61921568562845,-48.4385650799305 +17.9134342425175,7.67670837277702,-46.8571790950537 +14.260511568219,9.04831745304119,-48.8521786157069 +14.1899723289862,9.06937397608204,-47.7821100224975 +14.1348823923241,9.04652534899223,-47.4566527891201 +14.1791842634848,8.8554205548635,-48.9985648429217 +14.1754993395512,9.13827218436446,-47.4766163665426 +12.4369550985664,7.01895544479132,-48.1907260160379 +12.3978980298236,6.86845679508255,-49.525160409073 +12.4437073336855,7.22863933744627,-47.1354998587215 +13.9592965142987,8.84041326199337,-49.526466993627 +13.5381436694464,8.50082504652835,-49.9279409364277 +13.4840713947636,8.32494156586611,-49.1091912555427 +13.4380526567896,8.3064423669488,-49.8907750805804 +18.5907173268691,7.5710776567108,-46.6200608905433 +18.593038713137,7.56985625558968,-47.3684421859772 +18.6192849882804,7.57573516398921,-47.0119105517119 +18.6271450094737,7.69114838481415,-47.9623027816802 +18.6187668523466,7.77781864619723,-48.1837147500447 +18.7110195189356,7.82805214884149,-48.7213769371432 +18.7387710775007,7.86995203643908,-49.7182768926381 +18.7458010328288,7.87850513606519,-48.6199712626679 +18.6736059046517,7.90268103575409,-48.6893758744961 +18.6689695251222,7.8052495192573,-48.1182386468533 +18.7196784018921,7.89722409412621,-48.8032199320386 +18.6328406593252,8.00828788548202,-49.617404484439 +18.5917962189936,8.20930774242773,-49.0891337334882 +18.7537409561312,8.47853031212714,-49.6125605925184 +18.9056556203564,8.49667647558212,-49.6835850031725 +18.9742065785785,8.5504348879955,-48.0664947381372 +19.0287984473945,8.61950544018462,-47.4370149630981 +19.0626497207802,8.65670558454295,-48.4536839310003 +19.047473843914,8.68267431944015,-49.2725612572559 +19.0781127254169,8.65327126679955,-49.9998858051883 +15.0770904780716,7.13252367193633,49.9865257370109 +15.3617256983192,7.09367262158953,48.131463898938 +15.2258331558505,7.11553540172962,49.404729080944 +15.539836535368,7.02451697006093,49.1263227594293 +15.4322490818756,7.02984752664492,48.5093960250154 +20.0105064336796,8.5805702347534,-47.3861221383347 +19.96837723269,8.48834043211874,-47.096468549289 +20.0525942424731,8.71001656709865,-49.3370688602011 +15.7582605897626,6.91358497643709,48.8934160875328 +15.6888381289238,7.02938960315671,48.4118721419425 +15.5786508880856,7.25672256353647,49.3868964129999 +15.5652748072216,7.18778834106955,49.1072462273468 +15.5715497808909,7.07458062847141,48.8872966919241 +15.5920688453915,6.97334762013377,48.5148004302663 +15.6244052475429,7.18891049310642,48.7345734396704 +12.4385962181138,6.98608574113234,-45.7933774101347 +12.3442412433416,7.11069635486273,-46.586895636749 +12.4069803808028,7.04691303411332,-46.5831963428767 +12.2856527454392,7.27039351980877,-47.6491359605908 +12.2078122329802,7.45132839173986,-46.9396720262917 +15.5742745261488,6.86276680260071,48.0538555291264 +15.5353513317249,6.80977095740882,46.7094009098998 +12.1685445870045,7.53555036140205,-46.4236636959244 +15.559902146932,6.76876697090486,47.3750625750021 +15.5072613230886,6.82161153846488,45.8711105916128 +20.0123665941017,8.36857117467304,-47.0846042416534 +20.0888580461071,8.28257418049912,-47.4077103591148 +20.1291269994435,8.20038956900713,-47.5886762171869 +20.1067921747643,8.19672819719555,-47.2393626159368 +20.0865823262277,8.22427893093224,-47.8999531448179 +20.0851478700424,8.25692716149906,-47.4270946127159 +20.0308099427552,8.30239330138014,-46.8397187956965 +19.9203469458348,8.2813458970456,-46.3198596645687 +19.8646176538685,8.26194027468742,-45.60674166877 +19.8460417185412,8.33713648125152,-45.5536974588136 +19.8127438081435,8.50514949012559,-45.3994084504261 +19.8406232661527,8.43771649713262,-45.5162117300671 +19.726430172617,8.71586516874542,-47.2113492412008 +19.7016553956178,8.66082685177838,-46.7476600502081 +19.731350935573,8.58131626440792,-45.9929831816998 +14.9079497798066,6.67954013713716,45.4037032723433 +14.8778804816119,6.78996953061739,45.2868059383862 +14.8402722169709,6.88539418053244,45.2304412830456 +14.7705057090264,6.97104318330595,45.645161337155 +14.7071488739881,7.00641371098471,45.9844422507625 +19.7947620476824,8.64013864383972,-47.3217402285713 +19.8584403807419,8.53838480161388,-46.1472276866055 +19.907862588749,8.42627926370942,-45.412589677649 +19.9017844553773,8.42264097847594,-45.6731245991418 +19.8764790592379,8.47527221932039,-45.4483926573817 +19.6338860113363,8.61239827560258,-46.0123503362392 +19.5699025580097,8.57699146180131,-45.3668273654411 +19.4992137334984,8.55517281657972,-45.208248932986 +19.3659536268012,8.47013002248718,-46.8239485726507 +19.3468593955577,8.42805925838134,-47.8371016847504 +19.4008773323442,8.38831221401367,-47.9376682676686 +19.3933525554525,8.50865628365715,-46.0107632267575 +19.3745667064818,8.44924390117375,-49.0783920126506 +19.3271955953735,8.46252005654532,-49.0287940737234 +19.2415275638062,8.460304092527,-49.6597827534773 +19.4297230901025,8.45194589223578,-48.958160060069 +19.0302660904855,8.51924753971949,-48.6849117727821 +19.1416432183447,8.38790997668646,-49.0560507087662 +18.9810784819984,8.72337967977509,-49.7190082109526 +19.0932116051443,8.49890069656661,-49.3196142731198 +19.0013294098904,8.62224791095945,-49.1208559538488 +19.1313732948062,8.48151526977436,-48.8302359661323 +14.3729048265073,6.83338484687443,-48.8631061043441 +14.2718330525424,7.04284494501689,-48.8343263734293 +14.1946870506099,7.01560465105932,-47.9526759975124 +14.1188789244141,7.01363172092134,-47.4059629408324 +14.0488771326714,7.12114607921547,-47.1927176626655 +19.1311128679861,8.48520207312644,-49.9462238645487 +19.0024907093556,8.69468069754765,-48.2977651154733 +18.9218392251613,8.70313447355793,-48.8919266080187 +18.9015416557769,8.63664757634381,-49.4834165357738 +18.7566684242951,8.53581015216509,-49.4359756742694 +18.6958187620529,8.48308187480961,-49.8161105103038 +18.6891409437635,8.46362288066823,-49.3463862412825 +18.6981419150589,8.52213224250129,-47.8991095319678 +18.7202007921894,8.50032316769811,-46.9209191668046 +19.109231573606,8.56079449507665,-48.6150970266656 +18.6949259957316,8.36377930471303,-46.3496687409041 +18.7375625157082,8.46561155702639,-46.3692280592446 +18.62432751553,8.14588935809844,-47.6165905565348 +18.6941727612541,8.23293182359307,-47.2884855169155 +19.0536750081758,8.59142264667295,-47.8134091076355 +13.9982342618582,7.09458228601383,-47.8145394165014 +13.7583431275428,7.12899200454663,-48.05751251948 +13.7018791652327,7.10746664017903,-46.5081243032579 +13.6763656519174,7.25315039675405,-45.4722033444707 +13.7045803857534,7.49470635785191,-45.4111082984619 +13.7082632435474,7.64107859625613,-45.0414759181446 +13.928277131505,7.08157663371419,-49.5274613295134 +13.9560497843189,7.41844369862424,-45.1421245142604 +13.8244596313142,7.55916329432509,-46.0020571823922 +13.8796666570465,7.47541515154601,-46.0645634192426 +13.8278788865772,7.49784594868779,-45.6322410989128 +13.8060550685888,7.58890675657846,-46.1875102599683 +13.8133621162331,7.95519355382897,-45.6505765846722 +13.8876319258397,7.99250530090793,-48.1314455641212 +13.8475935848248,8.02722944092665,-47.1127991009186 +13.8898294023056,7.86369661520389,-47.8250743804409 +13.8768250624158,7.88434226868038,-47.892100896807 +13.8682832668055,7.6427844453108,-47.3781197042047 +13.9040787469879,7.73294257425329,-47.5687126200116 +13.7827765286427,7.5210497507265,-48.7890161668654 +13.8351122935391,7.62891131834543,-47.8808816679248 +18.50028727725,8.19471740438052,-49.8697369538249 +18.5152538422277,8.13908798070828,-49.1001168257508 +18.5494435514313,8.12298496413971,-48.4033510947797 +18.7625138791914,8.37601085696816,-49.2747926136882 +18.702573627551,8.49394840141435,-49.7458954368997 +18.863395012902,8.23476389781504,-48.6845457970237 +18.9693303935279,8.22055247456179,-49.0879840331491 +19.051265399278,8.11841050772943,-49.2555521093099 +14.0118342929878,7.17219066599651,-48.38932896239 +14.0220771573668,7.11922658426644,-45.9832869067229 +13.9338709914454,7.08466446465188,-45.4818343298807 +13.8569724273914,7.02052235259275,-46.2832867834361 +13.7458840849372,7.07935217064178,-45.9604814995361 +13.7847975146766,7.07777871612198,-46.7538688137874 +13.7345283761898,7.06489736159992,-45.1169952613488 +13.6989997323538,7.02286423045764,-48.7295392771589 +13.729236122365,7.01484183249942,-45.6298305816032 +13.7381745131657,6.97994822438298,-46.8739049939836 +14.7758462445699,6.58335428565822,-45.6133076338712 +14.8231437494154,6.56102181065743,-47.255094136269 +14.87638379654,6.6183279609214,-47.5231136037429 +14.9340885324109,6.74686626595916,-47.1516769679954 +14.9766082997588,7.03269992838483,-46.8143802594557 +15.0436538141911,7.17083924757557,-46.6721632461636 +15.0397497861051,7.14080698636126,-45.2085303029408 +15.2561248105504,7.01672152238068,-46.1693739118298 +15.302501131644,7.14023571915929,-46.9700658311226 +15.3732361377344,7.13698518310184,-47.0572507011918 +15.4019780149579,7.09237736210737,-47.5216596153574 +15.4314714970418,7.09531881762383,-46.8131060333708 +15.4153723008025,7.08726277807468,-46.1733491279676 +15.3926749093111,7.0605671875733,-46.756227954667 +13.1938681403438,7.0867053132958,-49.8937753449157 +13.1633839200542,7.16540293013192,-47.5145546903981 +13.1120158744128,7.28365404400825,-46.9777895027706 +13.0829840094287,7.33931837253786,-46.852895656967 +13.1751879770472,7.06491754944707,-48.5533133094404 +19.2616220900619,8.07392902749501,-47.9897351071562 +19.3312391784858,8.05175963218496,-46.6472169767513 +19.3930652306124,8.03915261180167,-45.8982113144401 +19.4079557109997,8.02498002300721,-45.6866456508492 +19.5056277096342,8.06352943162718,-45.606476291113 +19.1628218553432,8.08917441281718,-49.5302017163168 +19.7074107734444,8.26103646107765,-46.9759562094752 +13.0597942594381,7.42404383939737,-47.3688828309789 +19.8782791414426,8.38843944325002,-47.9769966102432 +19.781645374982,8.33701141372186,-47.4388511513681 +19.5982494166497,8.17543383115841,-45.9533201283856 +20.0329749902258,8.40531437733693,-46.2013046254124 +20.0919044791288,8.36198921461606,-45.1432888950436 +20.0782872292087,8.44029262499818,-45.2574010347936 +20.1041659119856,8.49922593723849,-45.3563803022175 +19.9670584358301,8.3775847876654,-46.7520004434706 +15.344383987944,7.14775449451833,-47.2515325756644 +15.2438808927545,7.20190747510866,-48.9027084058139 +15.1617088281105,7.2795757800651,-49.9767156796177 +14.8544587592178,7.36118057491628,-49.8344733679744 +14.8084814846104,7.38918579133723,-49.8622868830722 +14.8184578525283,7.28120813228126,-49.4850652458041 +14.78607717222,7.25953894919189,-48.6710409143634 +14.7336596708959,7.21278774232637,-47.9537060570381 +14.670254714322,7.14376327898889,-46.9167089670982 +13.0972284213815,7.36811557807419,-47.1627604921382 +13.2040617494302,7.37176602999999,-48.8930446054879 +13.1619349658928,7.35894025489142,-47.6320717387298 +13.2388372913619,7.15898154090531,-49.3730460608195 +13.243155063856,7.32245273903729,-48.8300064858507 +13.2810605071415,7.40022485724187,-47.6245508908813 +13.3268079855031,7.60588156217811,-46.6518144831826 +14.6542369039414,6.96268129992642,-47.6832599142981 +14.6352217243968,6.9743718556434,-47.7089895579157 +14.5943528142733,7.00093757133368,-47.5242015309601 +14.5947329195496,6.95356859829106,-47.4720827324671 +14.5835217640058,6.97688044130689,-47.4873595456214 +14.6068039744805,7.02417589443775,-47.1053938189746 +14.664151855991,6.93702809043861,-47.1797099151433 +14.7265138007879,6.95155670149251,-46.61816467015 +14.7855997755597,6.91405187454556,-46.5184900366107 +14.8652613377384,7.02464920416458,-47.0880133014635 +14.6195765401562,7.07560565292808,-46.4155677845204 +14.9017600490506,7.08828412442919,-46.9306766254387 +14.6228728590007,6.99785273197737,-46.9778775775863 +20.0694091452579,8.55758515613552,-45.8424730201143 +20.0308570977679,8.71303649229964,-46.9317023940757 +19.9735997737765,8.74554848251383,-47.1451845419674 +19.9447512479964,8.6996901753544,-47.4252671209308 +19.8844794567138,8.57664159579721,-46.9673014654726 +19.8634183294407,8.51507984654545,-45.7384113348588 +14.9632652820608,7.06418135254449,-45.604288574104 +13.3781807811802,7.65184173307545,-46.4412469337464 +13.3263335468435,7.60450927286051,-46.9553265924969 +13.3684518953834,7.61406763075525,-46.39685661679 +13.3738684147357,7.69232869519306,-47.0150424728481 +13.2995275885474,7.60476441401594,-47.3127573239009 +13.2763492762768,7.56679964044412,-48.2280699216247 +13.1652536741004,7.64194235594878,-45.5808017404789 +19.897568149943,8.65218253137338,-47.3766892874337 +13.2202665243179,7.6133287232545,-47.1891707765209 +12.9768312460611,7.19846715370341,-45.2553412309709 +12.9406286145565,7.03952336238594,-47.5994222900424 +19.142697640331,8.00638797056438,-45.2081296310063 +19.1362756921767,8.21850118529717,-46.5521995519063 +19.1342685357232,8.40454689290307,-47.3424895201072 +19.0774623548872,8.60113031483475,-47.2658814904582 +19.1323195510464,8.5533638098788,-46.9356126325085 +19.0557312006383,8.57160321117717,-47.053039889161 +19.1453271454488,8.32982411237369,-47.4978210192189 +19.1822886841081,8.18041809150459,-47.8626736368876 +19.0502403487449,8.47069723491366,-46.7998233324517 +13.4806680428788,7.16528122924334,-48.9969188518908 +13.4344949682479,7.21521201904413,-48.4080139252761 +13.3660115551723,7.39693103818534,-49.4699694798881 +13.1887106485994,7.46135744904947,-49.5661068828114 +13.0548679458839,7.3350552661449,-47.6392817470643 +13.1418898924355,7.3888693280502,-49.0502872629345 +12.9531746941446,7.27589677358862,-46.2312378376791 +14.6688860032249,6.16768497892058,45.4410578225466 +14.7063834915704,6.1481751832415,45.9341818279667 +14.7558613078653,6.25421557345173,46.2705635879597 +14.8839121338016,6.26466605931313,47.5038523469034 +14.8083926087238,6.29736791029618,47.2253013143551 +15.0378492431263,6.04033235798397,47.2223358235295 +14.9697111139297,6.16095914092505,46.9116755376225 +15.0612441293452,5.96395320077535,47.8165570491309 +19.1864714609824,8.20076121265652,-48.6560159515457 +19.1502788354638,8.26075398679064,-49.7232358293975 +19.0751008834771,8.3521158594674,-49.5354306485849 +18.9862058461586,8.49878380021964,-47.781928726935 +19.0257032170765,8.43330748433292,-49.2045150650636 +18.9054642265266,8.49176197149858,-47.3981122262399 +18.9367602139433,8.54949850725862,-47.4307745943029 +18.8631236284087,8.3072783900894,-46.2129195278345 +18.8924733732595,8.4232710216353,-46.872707427795 +18.7883365242036,8.09704845717933,-46.7807070391816 +18.7729010563971,7.98209239519043,-46.0973144305689 +18.7490923221751,7.90625850363319,-45.4715749283152 +18.8273082076449,8.22805404577844,-46.977768114849 +15.0691222715322,6.15164199931763,48.8981134526539 +15.1169553326353,6.02316654096569,49.0591123948092 +15.013273363252,6.23693011264762,48.7721884020235 +15.1074447086059,5.98528620968951,48.0308128802931 +14.9611365027292,6.41569206039931,48.324276863105 +14.8732851605753,6.50710599830346,47.0705967633496 +14.802746373503,6.4784551266974,45.8226106037958 +14.020512829006,7.28178971710132,45.7343851800127 +13.7366766297201,6.69068513373579,47.9486190849494 +13.6934884052317,6.72585843935318,49.3946013284417 +13.6775380289459,6.84757436428499,49.7759711009408 +13.6973972913293,6.83511482674144,48.8803775963956 +13.7909003749043,6.83052599985402,45.7047509025637 +13.7796681578527,6.6905062990547,47.3526538486342 +13.8559022417295,6.67574727005669,45.8289889913569 +13.7189502698981,6.74841262872059,48.5042728197205 +14.8868561544699,7.26409735626518,45.6939372659778 +14.9551004031751,7.26699062460987,46.1668191095677 +15.0221177954093,7.31613757462779,47.0282174748481 +15.0833095759604,7.34510623345548,47.0613030717237 +15.1134101457006,7.32747397886439,46.5889299949185 +15.1503186787314,7.30030999750937,46.2623106851225 +15.1748356799531,7.25824315231634,45.67090425306 +19.1530901275878,8.16865874302916,-45.9409682535645 +18.956117759684,8.02653617106225,-45.935645555831 +18.6767427693315,7.97379710181209,-45.8317834183443 +18.5747004649153,8.02125916964166,-48.1481252453511 +18.8256080990631,7.97680624409409,-45.7561200281609 +15.2035290235225,7.23364116811615,45.670949700291 +15.2330304178058,7.25819145382553,45.2165297833054 +15.3361560412232,7.20544478535246,45.0128792217348 +15.3755873299025,7.20556470268035,45.6480917117992 +15.3416918190126,7.18622987117397,45.736942895591 +15.3603569289116,7.15312721174258,45.1027888685135 +14.156170065263,7.30796504351662,46.428930477532 +13.8462310541031,7.09187466161107,46.8231071025214 +14.0349668827927,7.23792392272808,47.0195285089687 +14.3139298453455,7.40627328473451,46.0233909469449 +13.716717528888,6.97354575322369,45.2444601938767 +17.6905359651107,8.24082488157821,-45.0467894975947 +12.7775851836605,7.26172240153378,45.1944339506892 +12.734947020708,7.20879250602427,46.6849618924832 +18.5259607088,8.13795867149008,-47.0442073371345 +18.4826996608907,8.13224763162718,-46.3907612874463 +18.5612627358802,8.0976211471135,-45.3295625384674 +18.4802272328244,8.11184917738753,-45.7048925827958 +12.4728917308022,7.10221570041316,47.7835527767046 +12.3653853915427,7.00238939027585,46.8166986725548 +12.2711699071943,6.90724991068214,45.4624587983535 +12.6475918089706,7.23229266887442,48.227449364707 +12.5637474685605,7.15324045113019,48.124400136104 +12.0787588307678,6.90311687548206,45.3942954894362 +12.051718487291,6.98942833701825,45.6134748799097 +12.0799614828333,7.03221492813182,45.9454068293135 +12.0715085204262,7.1297361883743,45.6604387988599 +12.0368989818274,7.28301200470514,45.4927671464848 +12.0743131988296,6.91439058479691,45.0309688887473 +18.2398787653977,8.39220998734318,-46.4475412329885 +18.1755132256003,8.40396000049591,-47.1301573142731 +18.1158990036709,8.35356422524064,-46.881483452685 +18.2862854294621,8.44148761130894,-46.2416809506811 +18.1461158209058,8.25379425448724,-45.0964488953377 +18.2272416462289,8.21330747950209,-45.343592884959 +18.2169121601361,8.39407243087491,-46.469780116884 +13.4256550644174,6.79665320163408,46.2399850227405 +13.4751430100156,6.71575090814215,45.7741334236399 +13.4803112020885,6.64364664270085,45.5292769172376 +13.8923895191079,5.9436979078132,45.0697968461429 +14.0311134129831,6.57377920041828,45.7560683280153 +14.000260327948,6.82128332714099,45.265881482606 +14.0234220632915,6.28816794339409,45.1325486811407 +13.9257376107278,7.08678562730534,45.9845755210417 +13.874855132266,7.29897730569141,45.2277412587475 +13.3310901774874,7.09875563777471,45.1164996954068 +13.2863471784349,6.97910331148338,46.2807500607751 +13.2368402990472,6.83784064880395,46.6106894920996 +13.1901708828968,6.71165477429869,47.078752532304 +12.9991679126584,6.56402631516677,47.139490363689 +12.8624513094205,6.56067420392027,47.8376029917147 +13.1157532895388,6.6150692453867,47.4371271788614 +12.7131277365382,6.56046252882033,47.4009570812269 +12.4580512149908,6.64837510402268,47.6622502086886 +12.2403736900557,6.62177082673467,48.5140569934113 +12.3399909632609,6.63865039290393,48.3105200681208 +12.5567133961778,6.61920795109097,47.9557927598759 +12.1783250244151,6.54941808626506,49.0279030805987 +12.1920698978716,6.39759911880877,49.0778698619241 +12.078558531719,6.22657892627075,48.8739397749104 +12.0057529711635,6.25625313241208,48.2050325592108 +12.1450636773832,6.25394531984804,49.3346422977284 +12.1923812237588,6.30721982039263,49.5403070341843 +11.914919195866,6.1951921379866,47.8455303527455 +11.9550888653047,6.23707180869855,47.5231031801056 +11.9470848297819,6.22390756475569,47.7950465064738 +11.8438765585797,5.99300888418396,48.384952139553 +11.8414911115927,6.03858244294524,47.7202703637113 +11.8655185120218,6.05647177820223,49.4445069578784 +11.8461569352921,6.11723126565678,45.0188185283495 +11.8907030468002,6.09924949322989,48.5199987573505 +18.2585926893984,8.49933738237473,-46.8204258761895 +18.2461610703271,8.59983783780339,-47.7184880172043 +18.2439487609236,8.63497129301541,-47.5964129444895 +18.3365338612108,8.51954133076578,-47.4470650615854 +18.3940641211422,8.38204600635485,-47.4570153523077 +18.4476615891542,8.30470260962652,-48.2126483065477 +18.4459376742761,8.23620485824656,-49.2374031651854 +18.2789049641869,8.59905316115838,-47.167401736481 +12.7112108963551,6.06090960197889,46.850285735363 +12.7276369456101,6.22765507725763,48.4310927978657 +12.6582489783321,6.05596918041596,45.6505406464147 +12.7710271119574,6.51727640372605,47.3934078718124 +12.8183322842822,6.69323281311167,47.0098393184761 +12.8804045885278,6.83599080200454,46.1873043447894 +12.7551626823812,6.38338289123781,48.4641869590176 +12.9438762090444,6.84456297708637,45.506372325326 +12.8601877828007,6.78075140567939,46.412401848175 +14.2691538613776,7.99782847549888,45.6787508788469 +14.216558486155,8.02058462090861,46.8310430931334 +14.196310717019,8.06358809366031,46.4636128218222 +14.1853439275103,8.12225065853555,46.0230674025875 +14.2016306573777,8.14800632098881,45.0539677117642 +12.9687012358198,6.81502289990299,45.4008461625979 +12.9538894821163,6.82558407190915,45.3388520481035 +12.9443777222513,6.85097895765723,45.3155320686915 +12.9164201116451,6.91069452563134,46.4777438795899 +12.9828049529641,7.05689927666811,45.5378833359716 +12.9807821880504,7.08244368259944,45.1168475562005 +12.9105387561796,6.97445953727868,46.0847403122389 +12.9367032007914,7.00216215930609,46.3126086190704 +15.6009927831311,7.65019597075996,45.911153013465 +18.3110211551793,8.2033303564009,49.2115990940037 +18.4269062798213,8.13294725313666,47.9829717835559 +18.4988243238911,8.07282780449538,47.7042638850428 +18.3155653420607,8.11788974648432,48.4204896569889 +18.5847893361465,8.0667749919936,48.4422538040993 +18.6201382711812,8.10729793101692,49.6818039535178 +15.7259161422099,7.72723138768137,46.3165969805621 +15.8614266063718,7.67328838167039,45.9478116479037 +15.9572781805378,7.60473940740013,45.3261364876723 +13.2206133925567,6.55397005235277,45.4323395259883 +13.239145807896,6.36210134083639,46.2664265553062 +13.2042510562598,6.23321237035352,46.9039993311898 +13.1500574806866,6.40520495532316,47.1958688807139 +13.2198002923599,6.29764639657026,47.1868033703622 +13.1617128369089,6.55758667589598,46.6880751300679 +12.9106573712459,6.81785180421179,45.544534614291 +12.8728271089632,6.7025693974661,46.625503223181 +12.8411270180017,6.58158006868986,45.9558867281499 +19.0148913035805,7.93008509500209,49.6627665761336 +18.9994203507692,7.94213256416283,49.1028520304112 +18.8764357079217,7.97365945265829,49.5721999867074 +18.9470453817133,8.0097435913723,49.2051714638134 +13.5528277891336,6.76444449204669,45.6594376209726 +18.5988137357569,7.57498365749605,49.6683244983946 +18.5548575869164,7.57821834952149,49.8853416710951 +13.0114558499847,7.40119428148862,45.8595145797002 +12.9092390063976,7.68786542570596,47.5525211024815 +12.8394466527631,7.7445750493793,47.1580055396156 +12.9998300129259,7.3000893339371,45.2724648536116 +12.9466671305898,7.57637476566217,47.3018213295028 +12.7391182905504,7.83284270792834,47.9324110391136 +12.6963606992506,7.73203103750492,48.1099088948525 +12.3558177031625,8.09575513849724,49.5553122414115 +12.3133437332479,8.26732714679592,47.6760882406807 +12.2678224138802,8.26019757083996,46.0995546708105 +12.7930909596261,7.87568668819565,47.9932336378424 +12.6216525840246,7.64349606632635,49.1047679501295 +19.3216534043548,8.36793675692596,47.7284920333841 +19.2777942851866,8.05761577542899,47.3129630819772 +19.2899029785749,7.77169277520174,48.2220823977967 +19.3879456030737,8.71455596320812,48.8687995747639 +19.4255180498402,8.6410638899879,49.2225417098925 +19.4278307288615,8.44647290771805,48.9669655638189 +19.4299510724186,8.36692716699567,49.735243524144 +19.4571234114768,8.20472891349161,49.7735413848529 +19.3424649616577,8.62598461382601,47.8431508457487 +19.2672817746879,7.4458584847858,49.0540032445795 +19.3836588318326,7.88455942764374,49.8470091084071 +19.4183281297909,7.79511365179486,49.069870055753 +19.4379817405252,7.81729524130204,49.7198360958215 +18.8236639049599,7.55864935066965,48.5918911548338 +13.5836175126796,7.49249101115964,45.754475686091 +13.6980017273001,7.32983687694778,45.4070248854351 +13.6251126623892,7.42704463686005,45.6749707030387 +13.8511197738005,7.26460955059244,46.8106962949775 +13.9636352933405,7.23752722602139,47.2720243915946 +14.0001294164875,7.23059539669173,47.1055037473663 +13.9998001640284,7.33946833943823,48.1885891858369 +14.0384313221189,7.29528222922845,46.6712697838755 +14.0863033705131,7.30162866389471,45.9197542741539 +13.7750654294788,7.29778465431814,46.2931222929359 +13.9092193624539,7.30715544176214,47.2859067158752 +18.7523566528477,7.34047647409478,47.2557791079472 +18.6278842336732,7.19334862385314,47.364489897026 +18.5592791087925,7.26693867792451,47.7116509889809 +18.6721494923099,7.17885512208106,46.5962015695997 +18.2943738956484,7.66177525054936,49.5635868228234 +18.4560123088843,7.4363469465594,48.1819752739635 +17.6617787201496,7.8672613320876,49.9664782929215 +17.5344287716481,8.08492765766049,48.7764011914284 +17.5766179621649,7.98561093038728,48.6529627801 +17.4665483515832,8.29133254277928,49.0874007756431 +14.5584824261761,6.82172017036488,47.6200561162971 +14.8078303140085,6.81988291202085,48.8832224362073 +17.3976685477514,8.36088046931573,49.3979260044792 +17.3829187201079,8.41558035764857,49.8841569460032 +16.8599930843556,7.85764779215725,49.949871646998 +16.7307148209618,7.98550632898674,49.6894461164566 +16.6287377985596,8.10248605718401,49.9335867648305 +14.8690743115628,6.84046570435376,46.2933788189184 +17.2605752923784,8.29215141237336,49.5882459222963 +13.2554762254753,8.53799706801814,46.1589363019271 +13.1833578382027,8.31388176809924,47.4593358371953 +13.1412470797393,8.11162289832406,47.9644805935057 +13.1607417204308,8.0485154030834,49.0408933464329 +13.1143386427008,7.98606769198764,49.183239376233 +13.0614102041772,7.97339348523052,49.0827227091285 +13.0480248750098,7.98559265770132,49.0002122505413 +13.0254438726293,7.9761561584996,48.4414230647947 +13.0565206236385,7.99473285840338,48.1248572751129 +13.1080783567943,8.01712735185759,47.7820427758139 +13.165883181492,8.08327025713628,47.8259059663235 +13.2718570546695,8.20066597659472,47.3468877873859 +13.3617834067473,8.28891321903253,46.36337116114 +12.6060924103425,5.93905654859119,46.9045590254266 +12.5381292656965,6.06776915540269,48.173323889599 +12.6536561212173,5.97446097807777,46.4998352512216 +12.4186843564113,6.37079552798234,49.4155487543084 +12.4865500478975,6.1965184589767,48.8439311583174 +13.4097348255496,8.35026711906965,45.7241200478972 +13.5069325573842,8.35292086039169,45.8107422510834 +13.6593211981015,8.36781543303919,45.9111556847209 +13.8420756790488,8.28935938856658,45.7645842621927 +15.2605598621813,7.39334486134465,46.0216310578696 +15.3443739653101,7.39170084864287,46.4870642697296 +15.3538419736151,7.41785597334631,47.1314110609603 +12.2931254554307,5.94743844929852,49.2528647719348 +12.3098689813939,6.17834987898935,49.6834601641703 +12.2770786409254,5.80865866508546,49.0740834682073 +12.2604733184464,5.69943089843092,48.9433569449742 +12.2683559343226,5.6900821161038,48.1831934133356 +12.2918970840733,5.77638446885969,48.669506298747 +12.290318913142,5.971975364643,48.1043723171662 +12.2857814145532,6.11492137349724,47.1513069473028 +12.2952894802796,6.154373582113,46.0576854026394 +12.2554299012283,6.24826914810427,45.8798069839884 +12.1669498844854,6.37509205962163,47.3791974890727 +12.2063435703691,6.26924499209439,46.9107649222303 +15.3891594981157,7.328351939453,46.8186762819419 +15.4650932476218,7.31510453738643,46.4917888737987 +15.2227437235269,7.59694343263643,49.3952379927985 +15.2027527431084,7.35781596551128,45.3913958383018 +17.3011771521616,8.03763834918234,48.2326418324563 +17.337002607096,7.83842392626302,47.0824725839594 +17.7480833620371,7.48506688734555,45.5372918751302 +17.8329816731051,7.60989918727137,47.0602894190993 +17.9114317778031,7.62521184188725,47.9323699194373 +15.506591631548,7.33783246935467,47.1153041568851 +15.5244333377003,7.47676335288684,49.467953100161 +19.6635639546895,8.01968894049844,48.758625432946 +19.7494576667741,7.86218364209255,47.0177787481073 +19.7938840608971,7.93219695956646,45.9428664687579 +19.743982726992,7.96367469523979,45.0070238106942 +19.7822884716782,7.86921811606208,46.3545134316727 +16.4013864488881,8.97984911264022,-47.4729305894093 +16.5906980023435,8.67007215029487,-47.1718058476666 +16.7981002356544,8.44492348536958,-48.6626372982159 +16.9161560231327,8.12494177835644,-49.1327701818597 +12.0781090203285,6.4536793237564,48.3434339132249 +11.952287279361,6.48877914169778,49.0073644656906 +11.9184590135754,6.50134918330388,48.171746597276 +12.0117866294519,6.4764204223329,48.9775948358698 +11.8007052666263,6.4748647100151,46.0794244330028 +11.7718428139589,6.54507672257942,45.6051183324523 +11.7751895090126,6.61191485470123,46.5783062912973 +11.8479345569239,6.4373923985259,45.8391169379554 +11.7994494740851,6.65099087554877,46.4485823370242 +11.8306316596137,6.56707718292107,45.0419666230095 +11.7766228743143,6.65669614426539,46.7261486953568 +11.7812624730431,6.46818730109414,45.7447636118561 +17.3411464707371,7.95889571769978,-48.5317771805433 +17.3591374142369,8.06091335402758,-46.9705756839002 +17.4398653541303,8.15159457044852,-46.7718351847322 +17.5178294244943,8.29095038622626,-46.4929265910648 +17.9437033658478,8.34360636109565,-47.2476260210485 +17.7329620522384,8.36656077918918,-46.8636004384049 +18.0945485602069,8.35597857903196,-47.1878389098602 +18.1774066136501,8.31779673050968,-46.7078345412317 +18.1880003919456,8.22555950449855,-46.0651093338584 +12.2048363858557,5.65156192881874,45.9307326204789 +12.2058336366618,5.5903586416188,46.8627669454952 +12.1722172951772,5.50048618358241,47.1247435706968 +12.1479830522612,5.40689886787954,47.4420708439837 +19.3552501601308,8.01898919807975,45.7560127655452 +19.3828890983411,7.9141235006318,47.0779385675392 +19.4108960182855,7.85435975391277,47.5108647746156 +19.4113828554057,7.73263380697979,47.7590978151642 +19.4175226330643,7.65193321941595,48.4433928145581 +19.3434072102751,7.67719691170532,49.4682600823592 +19.3973619587263,7.60051637038582,48.4429140915311 +19.3024688988975,7.93997434210616,48.6224335119368 +19.3073647647605,7.78585973591026,48.8239963884845 +19.2818052265908,8.01311997954063,48.5044723880027 +19.2924244572287,8.11748019362982,48.8963399390882 +13.4994862398755,7.46064602419782,45.3197486506259 +13.3939213731886,7.3060876081679,47.3478287158735 +13.3304544638958,7.29036190114777,47.8315057762997 +13.2458632269875,7.24642138115857,47.250333146233 +13.2122005375035,7.22201146429467,46.2951726352795 +13.1994357536497,7.30791900805299,45.8554952132908 +12.0786769429882,5.42614898669539,46.8063622459 +12.1166811646608,5.41477361173646,47.4203368352885 +11.9004080659844,5.65709962012804,45.1955829706341 +12.1367660996262,5.4263955277898,47.7230211602831 +13.3167917166839,7.6123326776216,46.2696244301002 +13.362816984474,7.62187572153469,48.7702469838687 +11.9849055836459,5.52852336145786,46.414757532314 +19.3467781707368,8.08166666161444,49.3632974875668 +19.4294295264375,7.9071402875866,49.7986829211334 +19.4764061467735,7.71440945729212,49.936137402411 +19.5682165016441,7.44115873194421,48.4425179128118 +19.5844601677459,7.61424827128335,48.2335649494018 +19.5827597206304,7.79093269641913,47.236760637336 +19.5361777892156,7.88877377697681,47.1237982609853 +19.5793103864731,7.8931921420677,46.7957288925209 +19.5909743116795,7.82463879368661,46.5907596665496 +19.579232300384,7.77387644042585,47.549929551305 +19.5660459694283,7.708251966961,47.7706741186276 +14.9726399898406,6.91494771807824,49.9723174023335 +15.1044017139018,6.8840743425596,49.6391414221538 +15.0774796580201,6.93787537372651,48.3677376734572 +15.0256192178014,6.97570815978879,46.6658021791533 +16.4211230670223,8.31932603064782,-46.2429824194332 +16.7814436453716,8.28578229259124,-45.1528049055701 +16.8423251515963,8.34971849678843,-46.9255142861443 +16.8863698832856,8.46203225701305,-47.1396848617315 +16.6592330895501,8.32001675450194,-46.1517368154239 +16.9363140079502,8.48099347664409,-46.9261283872931 +16.9728436840404,8.54205070640653,-47.5709970703621 +17.2247023058309,8.51421478062155,-49.3685635743152 +17.0905315197519,8.50695134273835,-48.5039526114851 +16.5462589705435,8.28786971637547,-46.2732590031136 +13.9528035607885,6.93129255090089,45.3898108318235 +13.8232661429847,6.88616782046533,45.7695315638657 +13.6851978431824,6.83203606918814,46.857113880692 +13.5378256630074,6.80468432731011,47.8559673672851 +13.2992566339393,6.83429456183359,48.6980685397411 +13.1961977175898,6.92704716460462,49.8409449442584 +13.403036178006,6.82737412415684,48.6229996225987 +17.6482199794171,7.86577178804915,-49.940183548563 +17.7230890788671,7.85983264031521,-49.0099511490683 +17.8598596146022,7.88067924841876,-47.0079925740087 +17.9263461441401,7.94041052552438,-47.5777506069779 +17.7793844346428,7.82875506300943,-46.9530699767745 +17.7715364041892,7.81742296943631,-47.8257052189313 +18.1197993204428,8.02739593577105,-48.005982306965 +18.0864736476719,8.07300900873189,-48.6632945956672 +18.0630447854176,7.99964510010599,-47.1486976177963 +13.5713829131771,7.68422337981103,49.0595163947048 +13.7836006088603,7.72345568458824,49.2528378644836 +15.2006234827995,6.55689823121322,49.0310147541483 +15.3002637016029,6.37114015327924,47.2759357819683 +19.5204913507242,7.66862283148341,47.4777136480127 +19.414739314888,7.64625898362399,47.7743420535823 +19.2709240001529,7.67479355959828,47.7234811590828 +19.1599613789724,7.8273050708749,48.3767692792377 +19.0744830787601,7.87698378849857,47.7218734062387 +18.8346604699509,8.2203162459989,46.6462187304313 +18.7379200881257,8.34806955634259,47.0561503513747 +18.5834122337381,8.51216342635155,48.7368041500551 +18.9450639337177,8.02257591999422,47.2049723434905 +15.4426733992284,6.29407734285869,45.188754764466 +15.8974779112183,6.56266048012805,45.0318704537084 +15.9424078578471,6.56784564408109,46.9987818120765 +15.9372541101949,6.47224621706849,46.1629393719584 +15.8951126306389,6.70459892469888,47.1048289275129 +15.8183848654837,6.90157577900577,47.9014368865699 +15.735512604268,7.1794241362849,48.2461440861016 +15.6652474274823,7.47835985477669,49.651833166086 +11.0096700600301,4.96205079042829,45.2437193321874 +15.1409602428608,8.0471908875991,48.3691595934237 +15.2528970580799,8.16603018384035,49.9165863034171 +14.9338868696916,7.80120412044584,47.7547077719712 +15.0247895608232,7.91084995815371,48.2019292445979 +14.8464250391376,7.57943217027616,47.4317984301256 +14.8842269968577,7.65699068027277,47.1897084295271 +14.8449190436775,7.47301842610017,45.9445999574148 +18.0565335353371,8.02186620479068,-47.0616211109309 +18.0454942183579,8.00315052181905,-45.5876969938938 +16.9937400630582,8.23367361173811,-45.9255663753991 +13.0937476352048,5.11454635057714,45.037670882084 +16.7706368180608,7.80302040862109,48.8029330838411 +16.805812511284,7.76857676623538,47.372285805649 +16.8902441598649,7.77226104273242,47.0720093602628 +16.750036470448,7.77395900347331,48.0730668387102 +17.0895778947517,7.85115693228758,47.152980551584 +17.1991051341309,7.92587472803828,47.8368031791802 +16.9774394147259,7.7914159405246,47.3099568223921 +16.876470466508,8.37954068979156,-48.3603362735548 +16.7082661427474,8.56454416657671,-49.8877475318034 +16.1031650106155,8.51349045410671,-45.6628683727253 +14.3061227239471,6.87359171732075,46.3254562226417 +17.4989384081211,7.98255186300642,49.6702787983027 +17.2656830398076,8.02148016155937,47.8355132747624 +17.3307629681777,8.02502417894423,48.284400144489 +17.4279260688114,8.06147473202345,49.4655196336178 +18.7027471349796,7.6497146506765,49.3193520475016 +18.7428360221932,7.68777858407423,48.3454218362978 +16.4183062835381,8.74645971537545,-45.4933236914811 +16.424733619615,8.83588605131084,-47.5224224923763 +16.4790927462296,8.9657813868971,-49.0682452002316 +14.211108347449,6.79470525418656,48.0633932411992 +14.0997586851864,6.62515576864335,49.8578252564934 +14.1351026626778,6.66940831366845,49.6824307524384 +14.0021458082841,6.56120830244384,49.5568935259153 +13.7969285514876,6.74035216688113,49.9024270670889 +18.8120613929246,7.71330241541909,47.59097434172 +18.9158212896351,7.76845304451441,46.9020402216551 +19.0534284759344,7.91203437692403,47.2037066031259 +19.2223193812131,7.88705696284738,45.3429041006371 +19.2414131611372,7.68997353991986,45.159276331145 +19.2904930590828,7.69035833047447,46.7863612698491 +19.3587272396453,7.72441384095325,48.5119292922333 +19.1542114600038,7.88793337481666,45.8307886002042 +16.8683688394387,8.44785118331986,-49.4070399929992 +16.9831265923423,8.4232374551214,-47.9704248847568 +17.0897320812418,8.57271133801867,-47.7399774385573 +17.295286885396,8.63390455930118,-47.4462340482874 +17.1550360983532,8.60942381923208,-47.2729561755254 +17.5222491628465,8.57627636905803,-47.0863925201766 +17.8839782872789,8.50273138584555,-46.7059241151942 +17.7323911634267,8.53946487063009,-46.5537964982528 +17.9999902576304,8.54890529384099,-46.0344189356661 +18.0709997097456,8.63521393061857,-47.0637575350536 +18.0755353532683,9.03288553263668,-48.4964745646232 +18.127283823424,8.94338499460522,-49.7701003655359 +18.1051196009298,8.80367831367666,-47.8044554561032 +18.0778386809338,9.11099504415191,-47.4188819856615 +18.1012604908191,9.15767317060781,-46.3472267389917 +18.2941088791479,9.07107454843291,-45.4128096623993 +18.3004992644396,8.98480647579826,-45.0008394963237 +18.1544676573296,9.11532195545771,-45.1371747629206 +18.2942899923397,8.07572445528495,49.8795583561492 +18.4053743407172,7.78786703084076,49.0942777219132 +18.3481616672013,7.90718123854744,48.8705541737725 +18.4207287983761,7.47377408519868,49.8745651069328 +18.4151367153724,7.38771758322767,49.7022778307631 +18.4288843633142,7.322378349082,48.9741817556709 +18.4586863082895,7.41868859611423,48.5635086699717 +18.4516703687183,7.34289826952763,48.3203418537543 +18.4711111318766,7.42356177833942,48.2601297300234 +18.554694660433,7.40553697379253,47.5042840173451 +18.4040690591165,7.59048310551655,49.1888666994999 +18.3634122538153,8.84925464436365,-45.0507645456827 +18.352917139686,8.84652699890131,-45.7886643872978 +18.2746432082628,8.78102099025461,-45.907143184772 +18.2021620262487,8.77560876282372,-45.8927236377874 +18.6287660853735,7.42313382193345,48.0391563881528 +18.7189579342697,7.4191432846078,49.2096711611248 +18.6805516016116,7.45221728396066,48.7041647167784 +18.5394910680926,7.46813159891467,48.3891755440347 +18.4634430889636,7.48162418698134,48.2727396250011 +18.3480144751635,7.49538994733138,47.879676566957 +18.2170690990386,7.54748097154187,47.4027515235484 +18.6321295682873,7.50307690182917,49.236818033516 +18.0828084614422,7.51161820582154,47.8844557240579 +18.0839361933958,7.50867481382687,49.4087623337123 +18.1193204269166,7.55398719747189,49.1967245200461 +18.158216651988,7.62537703948601,49.3008655794338 +18.2131750329475,7.65264122650153,49.3376063386212 +18.2616826978663,7.68295160507159,49.3023349437263 +18.1296833570278,7.48068870483131,47.5402234608292 +17.3713130281302,9.09403258682508,-45.6551597215279 +17.3023305168649,8.93231419202481,-48.5616583887603 +17.3031732631858,9.02018377938675,-47.1788924829222 +17.3471359613301,8.84265465842023,-49.2860191860163 +17.4193522766008,8.82778437332649,-49.831312625682 +17.5595259851357,8.77200224908922,-49.6792095494583 +17.6985229474969,8.65969796089568,-49.7634655742694 +18.938424105246,8.95115304735764,-49.9830953491539 +19.0873489530821,9.0422482349853,-48.4039411006303 +19.2628708512327,9.02476478930843,-47.1382795323559 +19.2024799505243,9.02871657721184,-46.9446610724068 +19.3464608642335,8.71866777169977,-48.0765325737641 +19.3688160643183,8.48549525858431,-49.1861502295368 +19.3040548862771,8.89174607331653,-47.0498172275148 +20.6830451341882,8.54898665694868,-49.1929184243135 +20.6259433274667,8.60817057347101,-48.5889121390126 +20.6126078613524,8.59847458044026,-46.6982153463752 +20.5940742934908,8.62648825736309,-47.3560195155231 +20.6700798281277,8.48858229714688,-48.4934640646095 +20.6282217479292,8.56288109050851,-47.3626387322861 +20.0751395416197,8.50718684245539,-49.0104663632976 +19.8669160570764,8.55558581589459,-48.3959016784492 +19.7295729804104,8.54160212504755,-47.2354313787309 +19.4513129023078,8.54244881321532,49.9009861175045 +19.4456472250937,8.69928500418264,49.530660474199 +19.4384497452061,8.84260139183296,48.653116284526 +17.486945783059,8.0973822053557,-47.7570445161844 +17.67553204945,8.2653185384493,-46.7573396024686 +17.4015216686593,8.12447040531001,-49.3799401159134 +17.9630631548175,8.67504735973873,-47.7372549248859 +17.8303149123903,8.4553478372397,-47.2029892088451 +18.0719491374937,8.93582144697026,-48.9904053046646 +19.4364938341586,8.85483431701177,48.5324228777968 +19.4284018991084,8.81649721188967,47.4522386450343 +19.3301559731101,8.90245347038846,48.1501875789097 +19.2240244407941,9.04845958517332,49.5142257643495 +19.4295263720763,8.8503060186001,48.0908294417263 +18.0300048934622,8.74286877409976,-49.9008707228517 +18.3803324362494,8.36972927756248,-49.6996241258222 +18.4411252156875,8.31691643191493,-49.6184865521197 +18.4368748451503,8.26913673759319,-49.8296679800578 +18.4128722444867,8.23548762142172,-49.8781882410309 +18.4029338879497,8.19754080379697,-48.3749548987726 +18.3923644348107,8.26115113576644,-47.5170929265142 +18.339836452012,8.37163429331782,-45.0935701899484 +18.3243120251499,8.33923812666706,-46.7237665155409 +18.2295378693216,9.14018697190789,-45.0603532437978 +18.2157736022657,9.09339624839864,-46.5358167712891 +18.2300592880337,8.94528795764209,-47.7382834944693 +18.1797726088415,8.87765825284836,-49.2780820916815 +18.1839104115108,9.15914319046405,-45.8170749480641 +17.8660900608886,8.67620176178071,-47.9608147184355 +17.8910567417341,8.67322997540206,-49.0855579776404 +17.8504086819722,8.69102887766817,-47.3834471002358 +18.0814331867864,8.63672094173329,-46.7904922274857 +17.9382342293084,8.7170194173278,-46.7962660728361 +18.241443761125,8.65952040114496,-46.2131080523935 +18.3092724425454,8.62664041847282,-46.026189368013 +18.4532173087688,8.68077463540065,-46.5036557129417 +18.5984148788006,8.71817093264507,-46.2294529731724 +18.7707898732417,8.71257369839504,-45.5643572801367 +18.7072689918599,8.71382728621965,-45.8339826017064 +18.917451097493,8.50108919156036,-45.2065988113457 +18.7267569180971,8.71402941938541,49.294206661497 +18.8413222301947,8.61457865018495,48.599464823654 +18.8414893265278,8.59788988861583,-45.1153288390905 +18.9322925486319,8.52213622259083,47.3083341395879 +19.7446848677949,8.40749022429378,46.0167638059891 +19.9634636583255,8.28314416310304,48.506702737562 +19.9893150834497,8.20376879041134,47.5857928282588 +20.2090041502561,8.05891949249043,45.8640641131241 +20.0786796946679,8.13004725259279,46.9597780403416 +20.2694550843244,8.03568477526587,45.5926607786243 +19.8691557601885,8.35339855760708,47.4869641703536 +19.0632022791882,8.52977974339317,45.9477392110762 +20.3053629238361,8.10584180658347,46.0784311805238 +20.2926152031659,8.07014472584538,46.501494144925 +20.2906185657402,8.09284673142622,48.4747171424968 +20.1967270664039,7.9633997489298,46.978618700995 +20.2664617523404,7.96177509150858,46.775606608578 +20.09452924831,7.80491249693379,46.0716121884238 +20.1383823700608,7.93048749287108,46.888934768361 +20.0726200675416,7.81219855535634,45.3014240791431 +19.8529827179249,7.56844162727332,46.0376111135537 +19.7922788744166,7.65019253764949,47.9282399752653 +19.7378060542734,7.62463147414867,48.0762552872718 +19.7647122741158,7.63343661004421,47.9859384155121 +19.698873272982,7.65574041187555,47.1673202996758 +19.7650811283697,7.80156705396956,46.3702434323599 +19.7025154563734,7.75191394216365,46.4186597340983 +19.8040690664654,7.98032104422415,48.2953647422797 +19.7882366040744,8.02979223472954,49.222188685413 +19.7111448172639,7.99212235527539,49.295380952651 +19.6208982857437,7.85500444631772,49.2571873326896 +19.5640551940425,7.78303249331845,49.4691333406972 +19.5187811326946,7.72856082359384,49.3685437500828 +19.5734437351048,7.7764044827192,49.0312083117335 +19.7220960962586,7.56890821862278,47.3427699879184 +19.7905813426609,7.97459557241108,47.6898013222347 +19.6894588536956,7.83665779175199,48.5295327274231 +17.5210863081973,8.80665157471835,-45.3609551372392 +17.4613506713621,8.61767027753891,-45.5812866493127 +17.4483150946989,8.52921694774368,-45.1751483591082 +19.8280848786517,7.94520320160613,48.5051118112654 +19.944931907787,8.07431188616193,48.9809829024519 +20.1525335346694,8.31252051678878,49.1775554965279 +20.2966785724971,8.28922727336151,49.5945858279646 +20.3925628074881,8.33303524456084,49.9741113057158 +20.5817761508222,8.20273740605271,49.871591960249 +20.606193693432,8.22196054299602,49.7504636185768 +20.554711797897,8.15845055632157,47.9698875127682 +20.4903177403374,8.08714911564014,45.8707621378556 +20.038143878624,8.20534569806764,49.0096383420646 +13.926157092575,6.70893615495345,-49.2873103547223 +13.9085759992572,6.90012557495007,-47.0655735415798 +13.9087595042712,6.92771992312081,-47.7115348187141 +15.5331265436684,8.23729422494894,49.2629563766313 +19.2903762791429,7.31246676009495,46.7631359623384 +19.3244197571091,7.17657809213506,48.5393108373481 +19.3739438068295,7.16887024702488,49.8822982776211 +19.3714587720828,7.068424671162,48.9008711036316 +19.3643765651648,6.87868277754058,48.3117836545499 +19.2644218730407,6.8148096023058,47.5000872323952 +19.082652051246,6.73061081939708,47.0193321677597 +18.8807573803745,6.76163235250102,48.0438492898224 +19.3664599885834,6.98477570432247,49.287074008409 +13.8909287492757,6.92080995846447,-47.0587377635385 +13.842684516842,6.86097632117247,-46.858252708043 +13.7248152520147,6.79534393034084,-48.829807051452 +13.740335783507,6.73231058938788,-48.8665591192938 +13.7879954618176,6.71590431152382,-49.1538380960769 +13.8184125545172,6.68754537093148,-48.1113599292942 +13.8479777179763,6.64038768457152,-47.6137462309561 +13.8646520306001,6.58499573965992,-45.0954849714787 +13.7807378390428,6.80587091527621,-46.8033079723693 +15.5159350635581,8.14862212543756,45.2295653869779 +15.445591651309,7.9026865875181,45.0884179531038 +13.7368617817107,6.80875785493205,-48.0365583203712 +15.5191364605942,8.23819705341473,46.5555328067396 +15.2840214135212,7.71466120752974,46.155640550514 +15.3596131928437,7.74477262582885,49.2442383488797 +15.3637270683816,7.69098550674743,49.7605590552391 +15.3025079618027,7.72172669220458,47.7660375638941 +15.4008597797774,7.5940260398586,48.1966889213569 +15.4247553035578,7.62778070950118,47.7448805605399 +15.5064637819811,7.73603847238922,47.2398291281048 +15.4391855682032,7.74247405260972,47.8311458786726 +15.5861748114067,7.67750100744609,46.924705646215 +15.6442074844643,7.62173644748541,47.9737369752073 +15.7114784171133,7.61003381103952,49.9041632409516 +15.8015998545894,7.64982492603709,49.9698974829772 +18.5980851104424,6.84021599878126,49.5373906354642 +17.8363622718765,8.38887701044032,48.6670343892611 +17.7992326394631,8.53955657113244,47.1689359990826 +17.8146658497096,8.61251150188633,46.6354164413277 +17.7864363079091,8.68346874842964,47.3971325982746 +17.7432757781457,8.76431287564628,48.8870222354649 +17.8403404591246,8.4702919760762,47.165821296195 +17.7680942799357,7.90951455474702,-47.0571014688455 +17.7177943356429,8.02146987233079,-48.2822383246053 +17.5737898725801,8.44043903319002,-48.3808808828103 +17.4463069998289,8.45057149069859,-49.5156986297607 +17.661343685887,8.23654668416237,-48.9521594803122 +16.6382267312682,9.10348869652376,-45.2404896778208 +16.6844548627818,8.94065521000761,-46.9342424169704 +16.781614984048,8.87482732667135,-48.4493892693016 +16.8649383046646,8.78365246508134,-48.4472497637137 +16.9820237119991,8.7510440252874,-47.6441276580506 +17.1241528743951,8.74603112037668,-47.0821574591403 +17.1899781979752,8.77885088993442,-47.5952360723271 +17.2693989450391,8.72141345250767,-47.9935225692095 +17.059324487723,8.78922701261537,-47.3075648658124 +17.3144459884854,8.72739293238198,-48.4913080367176 +17.4303670888949,8.84976165245011,-48.0526089990499 +17.3775510732938,8.75330060986956,-48.85017999583 +17.4897590726553,9.04940465518556,-48.3446430898628 +13.5614004906366,6.58102924530165,-45.8899030729789 +13.5607087884715,6.69302945670692,-46.5548072350652 +13.5443903673652,6.83529658736981,-46.714500076532 +13.4865454300076,6.84755598500534,-47.0707117419069 +13.503028040886,6.92119532389771,-47.37806064887 +13.5184406037395,6.76097393446426,-47.0069943267135 +13.5536629708423,6.653403719184,-46.9433004867486 +13.5480621998808,6.58459993955998,-46.7442112195475 +13.556869368082,6.55320297971956,-45.4608916653113 +13.5581704479939,6.60653866613842,-45.0312142293033 +13.5451202261542,6.64508994406657,-46.6245405943219 +13.5658243102976,6.74729747412395,-47.9947662877911 +13.5414380215703,6.52265379388761,-45.9773733652177 +17.6008861617714,9.26859000652948,-47.5257200460307 +17.5810950885763,9.16094382654084,-47.7857525505515 +17.5929758497853,9.34955665806617,-48.1211979477195 +17.5769053402834,9.34635965633538,-48.2898450889553 +17.5558510797475,9.33719617199668,-49.7100181714828 +17.3136615448216,7.81681216741183,-49.2602916679947 +17.3496173706781,7.71049100402527,-47.2619030277932 +17.391836050185,7.70343932383006,-45.2861044394655 +17.8933157558402,7.8755884546575,-45.4722649314871 +13.5300639971699,6.80025343359077,-49.3813984071896 +13.5305741935987,6.95117434880871,-47.3363107699331 +13.5467846187178,6.94248871712089,-46.6574934388664 +13.5870005602528,6.97569925254569,-45.4037780601214 +13.6445622268548,6.90105658796047,-45.2214585690027 +13.6250554321082,6.79874059496142,-45.7600299279738 +13.5846561769008,6.67121864095079,-45.3155807870079 +13.6979086143531,6.48036806810165,-45.3691969010093 +13.7475525206384,6.38389274712813,-46.2393680432632 +13.5777585123753,6.66799292412204,-49.0225195824788 +17.9349232397359,7.55977254303624,-45.7933461665168 +17.9428050672783,7.76223086484526,-45.534749572268 +17.9143813758506,7.33154241317108,-46.5938158018352 +17.9077644147077,7.11540053856778,-47.1978424006711 +17.9185815953864,6.91487324675905,-46.5375352096513 +17.962017798614,6.91544757621907,-46.2035083424658 +18.0035715271604,6.94017915391189,-46.8225634233967 +18.0064254685784,6.86553875376057,-46.7283235131762 +18.0077060459518,6.98235895767083,-48.2489173547644 +18.044243542507,6.93073665059687,-49.7538599414137 +18.0220293437274,6.93738980230608,-49.2382739125626 +18.0812686218045,6.70902625757692,-49.1876157418066 +18.0756166284114,6.81500336725408,-49.2574337723958 +17.9450038866001,6.85603325569531,-48.6984947766785 +18.033645388147,6.76531894910165,-48.8035831370893 +13.7660956840296,6.35800311692856,-47.7241273513337 +13.7636930448245,6.37370590264582,-48.8402784450701 +13.754842082277,6.48094071572625,-49.7140078619606 +13.6780627791622,7.21867420360351,-46.4142979818657 +13.7247943023065,7.13078593235518,-48.4778374524114 +13.6288378452306,7.27361405598984,-45.2280553880975 +13.8415610663141,7.14050517135635,-45.734149724029 +17.8431994607647,6.98865569131007,-48.4132696457795 +17.6904044790736,7.34823095888592,-48.7150074178376 +17.664047307629,7.34704571100876,-49.5275159718758 +17.6518363376118,7.4195414699322,-49.3143229315848 +17.6147891681926,7.39945868872203,-48.800625682392 +17.5587937454509,7.41067255126564,-49.3730872798223 +17.4827739048366,7.42420404238021,-49.9567610260358 +17.3682854169263,7.36126899174056,-49.0954693031987 +17.268588409748,7.3216141794405,-49.2005021979965 +17.7314637123477,7.10098148106549,-48.3028928120883 +13.8726692536781,6.77919067493271,-46.4270276653598 +13.9272478444884,6.85991667719353,-47.0782312709308 +13.9436900199372,6.94205092924857,-45.5059834806878 +13.9523705184258,6.90054260576973,-46.1149811655331 +13.2793130437864,6.53874702929769,-45.4040270109288 +13.2595315302188,6.65117991071255,-49.8310343339318 +13.1594896082732,6.83025395558047,-49.6298474369652 +13.1136783998614,6.91457559998541,-49.0216601786681 +13.0699850213405,6.89993734498591,-48.6364639111577 +13.036860017134,6.95714403338402,-46.2366177114567 +17.5959840703488,7.21955305934902,46.4000147516933 +17.4235803585256,7.24649788182226,48.0688351431799 +17.2650062353292,7.36993535271526,49.7619041035101 +16.0015092911099,7.92733497492095,49.4989327784588 +16.0060507858847,7.90011002713081,49.8981830375057 +16.0510699762215,7.75617103605648,48.3288812938511 +16.1029836719844,7.63783416062318,46.0811146525677 +15.8724640473756,7.95725220078284,45.7737338644936 +15.9443845079002,7.94317509244408,45.5784463340871 +15.7828387492281,7.93434047124489,48.1346824245826 +15.6554712711298,7.87666785987912,49.3314073114966 +14.9122209198417,7.15120944063252,49.1601143726864 +14.7882950020143,7.22515471736643,48.3338536348439 +14.6682853382927,7.23876744519547,48.4029358402607 +14.5505947124376,7.23280027772352,47.7841585199374 +14.463037771432,7.26619647855715,48.9801085172471 +14.3798172118085,7.22900478859246,48.7616873749117 +14.2963373222812,7.15917059092514,48.9368047133406 +14.1731654603999,7.24002725927017,49.3002065727606 +14.0957197330562,7.23345391419191,48.9126519850427 +16.2304541485362,7.86340002188243,45.1787802372955 +16.2558880772964,7.67860297526834,46.0382638058009 +16.2298592338018,7.55839044217806,46.8064125244873 +16.2121187010322,7.46692543438765,47.2376770735884 +16.1857812990423,7.39495865191327,46.7106342478659 +16.1280004381272,7.32370706153103,45.3379485783612 +12.9594094299983,6.96371618808112,-45.1109950609981 +12.9155726031099,6.98251705979326,-46.0115961420493 +12.783698963589,6.92636180251527,-45.9629848279465 +12.7244776604588,6.99698525107427,-46.5493982762722 +12.8560745347149,6.9091881296693,-46.177463683781 +15.5066224249892,6.41108043860979,-46.9873410125302 +15.3905613656513,6.5199887170081,-46.0766587805912 +15.3351664031136,6.73959284103891,-48.7799886654948 +12.3698777153117,6.63048612634977,-45.8531128462444 +12.3620157497563,6.7083886725118,-46.4311935863392 +12.3978122024542,6.71998214459588,-45.2289560922058 +12.3791880492284,6.80996656612482,-47.8726841189188 +12.370909573388,6.90109177848467,-48.6025087395191 +12.4105139291896,6.95880086245381,-48.5996667158624 +12.4642914357848,6.93365640071674,-48.5807635893164 +12.5056278641439,6.95538289227499,-48.2879638464626 +12.7039941193367,7.02960810502648,-47.9155935635992 +12.7458337594025,7.0526554479729,-48.7941771250877 +12.3640463480898,6.77275065639489,-47.7279529517747 +12.6141148033945,6.95650176323121,-47.6790200139318 +12.8739081860113,7.01469546189737,-48.982654061624 +13.0038043913697,6.99822470550092,-47.8235834494394 +13.1030244949295,6.8803856386726,-47.5329879619639 +13.1879217278782,6.71652509682011,-49.1836681125621 +13.2996345454944,6.60264027582608,-47.6635727197934 +13.4515562050239,6.45520855231185,-47.7514661904033 +13.6190725551461,6.49446440917999,-46.7927377792828 +13.7429089228507,6.54575673628467,-45.8866101584863 +13.8184181727227,6.57276313481217,-45.9605555568383 +13.9396383244933,6.51100019468968,-45.431778865501 +13.4008450427882,6.52513006912348,-48.808125937145 +13.5133849467195,6.48233504862115,-47.9520774919372 +14.233041977107,7.65335745135447,-48.4493511710077 +14.2468820214802,7.78292741181967,-48.7602502097585 +14.1847076710866,7.75665484908544,-45.82158370876 +14.159117662151,7.76506550412052,-46.4233197234004 +14.1363873522022,7.51972236997398,-45.8917655266787 +14.1593758307463,7.36414323390055,-45.1674312234829 +14.2202507472276,7.81257281754817,-47.5619253915725 +16.8692152615572,8.61906826843499,47.0120060918669 +16.9422908952242,8.65022999960768,48.9151133685082 +16.8146737696352,8.19355757204284,49.6832624914894 +16.7028399660798,8.08641251351463,49.7463342460335 +16.6748267517244,8.07284001234727,49.9501992638355 +16.768968715681,8.14563353894926,49.3966121080907 +16.7974466690402,8.17383236711935,49.4617723312622 +14.6216548990942,7.28742434766692,-45.4834053344417 +14.6936535140874,7.18205159652689,-46.5070123560371 +14.7391336732256,7.08255276547784,-47.6766791882544 +14.7514448252809,6.91823377010922,-47.4371197492072 +14.7052147182471,6.78429202012893,-47.1146782009901 +14.6936638598153,6.71700848446034,-46.139482508616 +14.6752617772099,6.72652139432068,-45.6584437585627 +14.7128661994205,6.91311197006129,-45.0474705104771 +16.3323853062478,8.19018025143832,48.4333315877149 +16.4891337890477,8.35028707753382,48.1974875917632 +16.6718541582676,8.46652220603516,48.0773978131159 +16.7912055372361,8.52678676978176,48.1677073769812 +16.2631323684003,8.03133374604655,49.6594811357653 +16.9154231265457,8.5681588580383,49.2128009655919 +13.7891270113058,6.50194769783947,-45.0392689267005 +13.4642194126794,6.41684183624225,-47.5619111486237 +13.3727244395633,6.55517563920605,-48.8487906513916 +13.3620710265496,6.57851349563745,-48.455517251457 +13.4115091656009,6.69890744664686,-49.1904389768634 +13.4081312047058,6.48800757565664,-48.1740414278031 +13.0672304682646,6.89490013688092,-49.7484918944952 +13.4045520481013,6.7244011727616,-49.1580586357957 +13.6320632040366,6.46095930708491,-46.3760534033041 +13.0455094848644,7.08277446614797,-49.5874902894748 +12.8675644083697,7.16481671974053,-49.2827450907647 +12.8412697761289,7.04210793905529,-46.6383377794481 +12.807028886585,7.03880919012617,-45.2133917744371 +12.7168741076527,6.95773208125489,-46.452863522733 +12.6127639022836,6.89625939271712,-47.1881663911253 +12.5999091664179,6.87557643971049,-47.9596433702084 +12.5890127688057,6.76020722369714,-47.1014186282685 +12.4913036481833,6.73609301411406,-47.6944401613566 +12.4992201196641,6.74986703199699,-46.547029955391 +12.873703361354,7.0856366393657,-47.5806186566718 +17.5614017167915,8.32182833896999,49.9250066801784 +17.6843557676289,8.29113888451407,48.1739186531069 +17.633526853314,8.28536299623135,49.011153988029 +17.7165750074242,8.33982327724067,48.2438217053857 +17.6561568336547,8.28688692978775,48.0062240561623 +17.6341733692092,8.26478715868332,48.7007411078034 +17.6005148717319,8.2077949675094,48.7708437245656 +17.5596794548423,8.18871666702323,49.1507830910689 +17.5139285692871,8.22374185916857,49.7006061181176 +12.6704481356988,6.85744623891602,-45.41585868106 +12.5366825685832,6.81604214036165,-46.4305030495947 +12.5847944437019,6.71109353382398,47.3766993401997 +12.5114662433541,6.61144708270231,45.3382996162193 +16.950654649008,8.23225629747089,49.3891052578856 +16.5522994339901,8.29423778009925,48.9740719163539 +16.6867949446373,8.20879441086708,48.4562615214036 +16.7938627624992,8.19085515056919,49.0933260627233 +16.4366779426309,8.37356294636888,49.329164779075 +13.7279830475042,6.56781082856062,49.5132382361098 +13.9481810400353,6.56930948458215,49.7430986637638 +14.0211166672676,6.57510089230152,49.263828673367 +14.1126765915659,6.83432845417788,48.8941893928715 +14.0557695732069,6.64127504166366,48.2637627430261 +14.2558583268269,7.43168800527063,49.216384405241 +14.305917346442,7.31523542864841,49.9846239886074 +14.3779445358742,7.16945740379471,45.5394152764296 +14.4592808656368,7.11259229726975,45.5198570568959 +14.3175815854634,7.23588635157717,46.1461136979214 +17.3688350878205,7.03636378077842,-46.7005187598211 +17.2248714584084,7.10623366430854,-47.1520778365879 +17.0212744757817,7.26126456547475,-45.7240716181198 +16.9148332204983,7.36577429392771,-47.7584811751131 +16.7956817597007,7.45637951541622,-47.8791360389937 +16.6960219979393,7.55977661657329,-46.6879368096634 +16.6062789663101,7.61300986138605,-47.0457524010685 +16.5747901454182,7.77467093910565,-48.2786893080581 +16.4173176419813,7.6285083562601,-46.1730968855037 +16.5542097087055,7.72283265515739,-45.9441431160737 +16.4052924640797,7.6449171847137,-46.8213885291551 +16.3909873395059,7.55261136655049,-47.2907295402291 +16.3136482220365,7.61840377298727,-49.7581374371332 +15.4192710453836,7.86507121948245,-48.8002640965989 +15.4627424230697,7.78423396671586,-49.2604702539203 +15.533479339939,7.68774032341948,-49.7098300694553 +13.6690888724199,7.8589031121039,46.6743495243905 +13.7144523676232,7.94394049784841,48.6284233445577 +13.6009400001938,7.85822632641592,45.7535139698995 +13.5472984200375,7.9066405108209,45.2057612599436 +13.5100935513724,7.99622959135375,45.307514313569 +13.4208772549987,8.11035691793858,46.7564953683947 +13.3061350424582,8.14684799222996,48.2391275407735 +13.2919282848404,8.11793785521374,48.4567505901979 +13.1832993936024,8.07495428484729,48.5859511008077 +13.1165151512085,8.12101004067327,49.598550754122 +15.0547296771115,7.17991523869953,49.7814340160006 +13.3274422253293,8.14296218791402,48.0330094962744 +16.1766539340944,7.49241113316009,-49.966167598013 +16.4958265598547,7.28344874049037,-49.7140129166739 +16.5204854116385,7.34537257208655,-49.2295392710746 +16.5386832550128,7.42444109454994,-49.5100953564062 +15.599383207303,7.65751436199879,46.9352157956134 +15.624312239438,7.55140493120937,46.2263140220403 +15.5075964417773,7.40100890396584,48.436373652399 +15.5067392711457,7.30789093754317,49.3176432772098 +15.5725329447865,7.38411517776336,47.8716776995431 +12.9341724297802,8.22695575641199,49.9786305035335 +12.7049047105472,8.57089463993075,49.7609266703979 +12.6436382618827,8.59508287479697,48.4232256661804 +12.684220663375,8.57723449445241,48.4569362010982 +12.746222178316,8.78452468339953,48.0915307499616 +12.684421569063,8.65280274926365,47.6281487003382 +12.758144174972,8.8854337445565,48.6228477162566 +12.7317544122814,8.71420769644135,47.3661735565674 +14.1811533257755,7.41007424885582,45.610340333492 +16.6950169002715,7.72869755443771,-49.1241047690003 +16.757355395358,7.55080702389279,-47.7020947141293 +16.7333255038826,7.66956165629531,-47.9040176022312 +13.4951899912443,8.93135349995986,48.7935350943636 +13.4420351322745,8.92659042838206,49.6855615417704 +13.5391892402537,9.04908314786618,48.4907045019221 +13.6527862553642,9.14975989166657,47.789753086496 +14.0198111469026,9.58817268073603,46.2757524023363 +13.7741789192605,9.24172895627947,46.9847645591983 +13.91063237292,9.43986418284113,46.6373828177136 +14.3466881741529,9.68434833761781,47.4693088333939 +14.5212324448064,9.54504483951725,49.0527158521244 +14.1576775247692,9.64796446004322,46.3542551574384 +14.304708463512,7.74368378161019,45.9406949453995 +14.2955855296856,7.80646865073353,46.6084436035427 +14.2578981383427,7.87302362502089,46.6833788512088 +14.3244151417105,7.90175995097902,46.6614730487278 +14.4679696574759,7.81348078569503,46.1349843420848 +14.6587543602326,7.82265585361191,46.2413866998488 +14.1751318479207,7.54319962368422,45.4175923741338 +15.1148978482962,7.87461762288197,47.5719299581417 +14.8732395734429,7.8142155973433,46.9434779680263 +14.2603888609934,7.90121468292484,46.5233185120863 +15.3565116101519,7.95692117007369,47.681360518315 +15.5588901745954,7.97081947792359,47.2329158393418 +15.9609474926025,7.92734896479658,47.0543413313904 +15.773628738844,7.99525540472982,47.2321254819503 +16.5610352347631,7.60774211683619,46.3970742078435 +16.2014696161705,7.78610770211345,47.0323803740502 +16.7927332971676,7.60155328771026,46.6527507538767 +16.440519160284,7.6139863932475,46.8405057814398 +16.0941361954529,7.88317735552206,47.0426883851854 +16.3355190566511,7.66215695757393,46.3362901299678 +17.0083880716543,7.56749794884895,46.7313007763476 +17.905613049808,8.3460898806339,47.912650030787 +17.8118609349302,8.21441123166618,46.2919812176637 +18.2035272489819,8.4746668040328,48.6301603051605 +18.4016782527411,8.41018844543088,48.8592381262682 +18.4791208449062,8.17758456293748,48.8471299512042 +18.0416506846533,8.36426282418556,48.0142377871991 +18.4907666342555,7.94909046176193,48.8496357207218 +18.2925367544897,8.45600927268615,49.1191825934012 +18.7263489803297,7.75994706593364,47.9607596830591 +18.9454909269652,7.68087673465786,47.9616131773477 +18.5605028132126,7.83192193965617,48.2870526083381 +18.9962944615212,7.59072412453387,47.4145413438679 +18.8689519899389,7.63695913990879,47.7881736713362 +14.7925317704553,8.64424249748003,49.9735691531075 +14.9657466935277,8.38539653240813,47.3182289274567 +14.8538225543108,8.47853706097872,48.9774279753196 +15.0941936707315,8.30749303811069,45.8145841350515 +15.2470490335955,8.27048253325896,47.5515937630684 +15.1924803630864,8.2466358718862,45.9504664753441 +15.3260929811442,8.29455557625997,49.3801579694779 +19.1128656091056,7.72541526005789,46.5419491449769 +19.0527120304656,7.65769958509514,46.8332172198379 +19.2420371220693,7.79851045340432,46.0698711828238 +19.1727181774545,7.76056003827119,46.5459605097337 +15.3726950986628,8.50378095840706,45.2290121158227 +15.156949584709,8.66747702098843,46.5655408161358 +15.2688728625391,8.60948713746638,46.7433670007192 +15.0191074139629,8.86116552375776,46.9780916329492 +15.0718225477357,8.76041271159272,46.9399159869187 +14.9745346978526,8.9767571870699,46.9461508560754 +14.9984930162202,8.92075286238736,46.6732919705107 +14.9590052044202,8.94899795405876,45.9317766782439 +14.977120653822,8.92218047565842,46.5581041481602 +15.0019023317603,8.72227392548389,45.7079937102098 +15.0776399636454,8.47930305779548,45.1455530716694 +15.043800940989,8.55608245462002,45.1662057449208 +15.1403052322332,8.27759278375683,45.3497107718201 +15.1245674676123,8.29441928407851,46.1198875034926 +15.130487850316,8.2114785332785,45.3235206550815 +15.163696785664,8.31877835005214,48.678115910134 +15.1430776344355,8.35238934920518,47.5114302060284 +15.3077445233218,7.6724399789973,46.8489676488108 +15.1860707930321,7.86285256303862,49.7619989114521 +15.7566223184577,8.31535834671703,46.024315898236 +15.8152174324297,8.43333847055423,47.764894071947 +15.9101713495037,8.52059847838835,48.3316484154075 +16.0249182791046,8.54631596466466,47.7633618862152 +16.1540787455336,8.51977788871682,47.0318776156474 +16.2675848346523,8.49866876898398,46.2714703118816 +16.4249360058936,8.44481333386864,45.9705450129828 +16.5539569995384,8.35638621757327,45.8435638875063 +15.4621608882941,7.63849553956767,45.4030943272941 +16.6304956016212,8.17276153972744,45.967839682927 +16.6099387797383,8.27033402097494,45.7207804062088 +16.6265033461852,8.18003361007564,46.5967128530094 +16.5728073791859,8.26139230915308,46.4537466739321 +16.5657215813554,8.2555175571146,47.3688007261126 +16.7435218510442,8.30114183331066,45.5003174056487 +17.4509079376825,8.48960118168204,47.5974265458507 +17.4563672939514,8.41299212229013,45.8878978621165 +17.5144674222488,8.64689700720408,49.0283956870867 +19.7814584478705,7.31976051023301,46.0259373359573 +19.7488681899079,7.33762705319192,46.5861023253596 +19.7638880297812,7.42766007164207,47.1638795235526 +19.7639920434235,7.51482008699829,47.2046092933089 +19.82227278249,7.64293951394597,47.1658099961676 +19.9458124929727,7.88690928025005,47.5068019585462 +19.89160438888,7.86351143044417,47.7783447533185 +19.9194845114042,7.94708817100037,48.0093339413405 +19.8771302257017,7.97130030259932,47.9010767115403 +19.9608158916061,7.88311925669708,46.462482745607 +19.8811854261873,7.93130970979825,47.4815222131526 +20.0020508093636,7.66661886577619,45.0972210716845 +20.0001432028821,7.80050567031144,45.8941223179274 +19.7570912406096,6.84449681522698,46.6119443329124 +19.6371964586688,6.77977613566188,47.8200562730003 +19.5359753726224,6.82763391369426,49.1608517190749 +19.5183938598262,6.86133858642088,49.8826441506772 +19.8329548809305,6.94276795139937,45.5564430911369 +19.4925909294847,7.32786178057561,49.2104070941505 +19.4585692655574,7.36435164942922,48.6795643996883 +19.4189786022534,7.41866082493238,47.0644645282881 +19.3702163297086,7.456556203825,47.0183807343762 +17.7117077817512,7.44144307719526,48.0245032267089 +18.5387904472708,7.49657401943843,49.1469660545208 +18.7810818015076,7.36738730685962,49.8463220535799 +18.7548281684465,7.3402388143153,48.0199297785666 +20.3168458459434,7.64123868620004,45.5682723651726 +20.4882998457474,7.53056541304519,45.2288904903288 +20.643503995666,7.51373993015684,45.4141958648016 +17.6306298149447,7.86599599697992,-45.5396289043478 +17.7113284274037,7.8940443585704,-46.7630297073853 +21.2693220425515,7.4086669857909,47.7812079715645 +21.3665961376939,7.45220344367702,46.6557723002715 +20.7697188608777,7.05818404084831,49.5213598542658 +20.6121188479576,6.98560704626578,46.9613215355972 +19.7865420137806,7.7183293577925,46.1379874537375 +19.7432435006464,7.82968720753443,46.8198408117728 +19.7144533569752,7.92706095390121,47.7728125892643 +19.8061965921215,8.25175632709749,49.1032593037255 +19.8195818953727,8.2446991032983,49.496031340484 +19.8440682725962,8.22267055163503,49.8249584347826 +19.7693743739314,8.02621530129232,47.7245327650951 +19.7964781595429,8.17989830863012,48.4789582922292 +17.9756140519964,7.96946965918505,-47.6897395535902 +17.8874849869505,7.92612971218388,-47.8698631911884 +18.0231437329838,7.92735613443898,-48.6661376973642 +18.0607625741855,7.94344328601665,-49.4996518416173 +18.1193886220118,7.92078957386766,-49.2500153424101 +17.8178986694224,7.93088370619159,-47.6667960625634 +20.0302062702573,7.82168212514985,49.7359391176233 +20.1294193720289,7.58861055391212,47.9584858004114 +20.194098146121,7.3684868566802,46.6766745304376 +20.2415070954147,7.17094656842939,45.3136983712276 +18.1201657356985,7.9446041401321,-49.38574402331 +20.3524107820766,7.04865910064385,47.2672137201052 +20.3118973553754,6.96328212112571,45.0014603756619 +20.4147837091013,7.12402764869511,49.2971312427499 +16.2520671536896,8.56093950972476,-45.5051342805279 +16.324290597955,8.52547504790432,-45.7750033807793 +16.448673924303,8.5190371800344,-46.1228516193555 +16.5934878594504,8.4872720835429,-46.978582757038 +16.677032460897,8.38475895640298,-47.5555828401351 +16.7959590443714,8.27929202277663,-48.2338614094225 +16.9484839062865,8.17303320541638,-47.620911428934 +17.0730923449124,8.11885835809796,-47.9034361599378 +17.1483566318596,8.02838016158896,-47.418564725856 +17.1961560533842,7.92317967676666,-45.8443938177967 +20.1247738144838,7.34734872886735,49.8723434611999 +20.2463664080727,7.84316217157266,49.7979175036581 +20.2086526460952,7.71694127289227,48.7652121449657 +20.1825695608552,7.53244496541323,46.9177543963748 +20.2077315219936,7.3847671658329,46.1868984569272 +20.2034147976273,7.29611024408292,45.659007563229 +20.2342368194958,7.26892125458884,45.7134939280649 +17.2321556058816,8.13258550941831,45.2888673983759 +20.4497768680937,7.47368880474023,47.376512728576 +20.3427961158136,7.40834011243024,47.1053360254353 +20.2567638843343,7.35566824994271,46.4504224519254 +20.7528853310772,7.58849590409821,45.2958362147274 +20.6215267670374,7.53213002939957,46.7488110324328 +21.0860455346715,7.11225728306615,47.0756439943294 +21.2176322157415,7.11565816019746,45.7253507567766 +20.9474666437608,7.02066454655388,46.8673529593999 +20.4358705355982,7.11289278744402,48.4997259380071 +20.7413811360954,7.05514267235887,47.7411522861345 +20.1971255852696,7.15634059241934,48.2109480925336 +20.0587456458167,7.3207976878673,49.0030710690726 +19.9223462786908,7.42470733333528,48.7333159432811 +19.8495709428933,7.48216003339761,47.9275213275702 +19.7545662593236,7.57094278853002,47.8445754262391 +19.6176563327783,7.48091129323772,46.9981983508645 +19.492985324527,7.46495436589543,46.864163416565 +19.4678703563967,7.27140383009331,46.0946665494575 +19.542471701966,7.14490879537054,45.428803986443 +19.5779433812959,7.20685791946412,45.1897408294975 +19.5925540199748,7.27802591404931,45.391463189531 +21.4115192317991,7.25612571825898,45.8383464979793 +21.4036403781153,7.29069312343371,47.1185853867674 +21.4094666510609,7.23183757218546,46.1543890876962 +21.367184830453,7.13268477127181,46.1840201841436 +21.2848342411055,7.12352920370299,46.353152872748 +16.5968942634955,8.83563531059608,-45.3305793612354 +16.506054574774,8.57611145416163,-45.4637336970005 +16.3934787669429,8.3982387597811,-45.8311641765281 +16.2786283695344,8.20611482763686,-45.2084734801104 +16.1137924713872,8.1721853543775,-45.3659402652519 +15.9492316732801,8.07658152386244,-45.1681153953887 +15.8779357622023,7.9927900396084,-45.5021674505716 +19.2602549421018,7.23040326082391,-46.1014705288194 +19.2516006697489,7.56081169661104,-46.9666025120635 +19.2128582515622,7.79565546567494,-46.4153882447974 +15.8494653575168,7.94604782933593,-46.4742969053544 +15.7647790184065,7.78954921701184,-48.781722994464 +15.8145952629521,7.90817188073339,-48.4223443655428 +19.1796426194469,7.83542241159716,-46.5350734054245 +18.679768857373,7.95541610404532,-49.2705902506827 +18.8213889534999,7.97245786318992,-45.8892645194052 +18.1982523672966,7.88239691940921,-48.8562081382538 +18.1957890632884,7.84376628993059,-49.8972512856948 +18.2341876703194,8.31958935708893,-49.4320532242719 +18.2042844732348,8.14394119768155,-48.4679445284859 +15.2072350007696,7.15123213479733,-46.3942055595858 +15.1763867785061,7.13726455834651,-46.0084674176988 +15.2573708324892,7.2492229237258,-49.213001304035 +18.1624513262515,8.49532832565512,-49.4001468186996 +18.2176078744535,8.45820950904031,-49.8748670933032 +18.1118527345623,8.50737352089133,-49.4610199735933 +14.8696710609872,7.49621773011731,-45.3214382449169 +14.879746163571,7.36233667181658,-45.2760511870895 +14.8461532605977,7.70874298649035,-48.3803519547932 +14.8463323267648,7.66481716138287,-46.672388794495 +14.8303824566052,7.755236483207,-48.8773916383559 +14.2893429739648,7.81935069737247,-49.9299872399318 +14.1674825979201,7.91504313681742,-49.6826705001341 +14.2158064985425,7.91899591833758,-49.6827865371214 +17.4969211083757,9.04682183859159,-49.7359361391279 +17.4747872363913,8.9540581851569,-47.4924097468816 +17.4386676738794,8.87005221216993,-46.7994480849455 +17.4092249159367,8.80431061363667,-46.7907035497027 +17.3142321212138,8.8078359406552,-46.9503324916957 +17.3645429349553,8.7981396661959,-47.1373494915691 +17.2501395644697,8.78975797214873,-46.6599085810977 +17.0407051794525,8.45858483832092,-46.8570325496291 +17.0472586800398,8.37113925332731,-46.6782954758667 +17.0376085816151,8.14926897035699,-46.8326807207309 +17.1596149253604,8.7332113604285,-46.7227391086046 +17.0817466936277,8.6164147569006,-46.3254244550928 +14.1372006329758,7.86086395670876,-49.0571836617846 +14.1452701535132,7.8141166879202,-48.6477533260746 +14.1458040960607,7.81414031138345,-49.3276324727149 +14.1694636276639,7.76366501631255,-49.1229782706363 +14.1850068854069,7.75956538457967,-49.0280335235722 +14.2386778890438,7.8032726839152,-48.9127957492152 +14.2967207608866,7.87746186889546,-48.9491721256886 +14.3183696710424,7.89875636836416,-49.2047034537278 +14.3627546785722,7.9108798744865,-48.939794137523 +14.4097143475801,7.92042372019386,-49.2387835779647 +17.075592130947,8.10133009286078,-48.2434462558405 +17.1850075049293,7.9836765503371,-48.8083808534452 +14.6970616106944,8.37766380593159,-49.7964631256935 +17.4973181632141,8.39688257889096,-48.8668280731024 +17.4229624972084,8.39944765662387,-48.6411691424621 +17.3531122787689,8.3321576160324,-47.8829010911019 +17.2998800817551,8.28157425446517,-48.0657815481726 +17.2331252638007,8.22974084717225,-48.2939608619963 +17.2341565102757,8.21509827363464,-49.5896186838454 +16.9768977944385,8.61110618064858,-49.6654020343891 +16.9182721179584,8.68823286083148,-49.2142374961765 +16.8109903929583,8.76690345488286,-49.0859311005936 +16.2882846640736,8.48539931437312,-49.5471281192838 +16.3233267326357,8.59105480164652,-49.7896426349971 +16.4846229288853,7.95842162507354,-49.5604016469793 +16.3607679998446,8.34662315314322,-49.9668212298912 +16.4476023175335,8.14259682467893,-49.4013146362863 +17.7013215564209,7.9328199296337,45.4495614193954 +17.6425625773511,7.89129376045762,46.2286854443379 +17.5599984690602,7.86069372238443,46.5433733586679 +17.5181399093528,7.87198666755493,47.1253786406907 +17.5363891066321,7.92439159279897,46.9862869728402 +17.620479467353,8.04564142440096,46.5431400145968 +17.6157997116886,8.13441557703474,46.7316414793829 +17.6113685701775,8.25023002653554,45.7579584003047 +17.5756564155268,8.20233087345562,46.0004432709798 +17.5074322990612,7.97693646292636,46.6182742427235 +17.42528623428,7.83018994686919,47.7380787818259 +17.326786951207,7.72215025574387,46.7368370897827 +17.215848867638,7.68359113518314,47.6576040660313 +17.1301325395837,7.67761259834354,48.8132334358937 +17.0587585970588,7.67790565999985,48.558486548127 +17.0273601652719,7.76470262133258,48.3381445569828 +17.0315518645696,7.81338238044216,48.8252155462496 +17.0607459791108,7.86777885108601,49.5008101746657 +17.0068639945122,7.93543457584614,49.7031125102807 +16.9511801669056,8.07490521278251,49.6153200230658 +16.969213645182,8.25060489868474,49.1730643511589 +16.9437795216044,8.37730961931278,48.4916752478909 +16.9567834446897,8.55242730968029,46.9821860349858 +16.9594962151661,8.64101076133567,46.6957340138393 +16.9315609547489,8.6113436426989,46.6622918479819 +16.9361533458992,8.655720446172,47.2566667568859 +17.0032786114397,8.60662745673471,47.4650237278993 +17.1295384856328,8.59554385935064,48.9360775997197 +19.5908641755298,8.07974077545023,48.4654493221612 +19.4906302803054,8.12919104582299,46.1098633397453 +19.3327495747185,8.13710329764902,46.264770975426 +19.3210750688079,8.15663918549569,46.4005696644762 +19.3949520929594,8.14804856020208,46.0553306919753 +19.3055237964413,8.19221586403366,46.660254647215 +19.5503341346981,8.14407943894035,47.4874717907039 +17.9020977560797,7.89445241481709,49.0614499783807 +17.9606914623818,7.84534340990955,48.6081522138812 +17.9342336315064,7.75962477122671,48.4815901132601 +17.9096118880652,7.79741104009557,47.4650534832266 +17.8720776257226,7.71281530095617,47.3244522382981 +17.6850820572812,7.70388030742861,45.3080405324433 +17.6433924039645,7.71799031157537,46.2804263069817 +17.5882552996493,7.81811626991343,47.487843259996 +17.5183369326132,7.82242939779308,48.2681048697178 +17.4441185838372,7.97065989340537,49.798038652539 +19.3326903329462,8.60453581038987,45.2774444206824 +19.4253826532977,8.86127552787855,45.1089192465229 +19.4103810449009,8.8674873218932,45.3078429318206 +19.4008459610969,8.15250412628558,45.1355474857203 +19.4005207754725,8.21795256171364,45.4413182471705 +19.377657185336,8.14932550281763,45.2744200321485 +19.3084296722361,8.40661466760268,46.1391089911759 +17.1027714541269,8.24046475682604,48.1331618518997 +17.0925041342527,8.22114630383465,45.0922684294549 +19.2267589566891,8.35520553304029,45.0193737591287 +19.1868982875906,8.48683492254958,46.2547642356587 +19.1583700816178,8.66686454938147,46.1030981445219 +19.1375694314726,8.60922690374661,46.4392222779001 +19.3417185714027,8.36523617977846,45.0340505950836 +19.5115570862135,8.5067380460919,46.949638505207 +19.6143392058396,8.53692472308739,47.7049868314281 +19.7094394866205,8.51740695325418,48.0261531423575 +19.8167024718246,8.51014791120583,46.9408734635326 +19.9708580829315,8.64061304794408,45.7880173095812 +19.9189555608791,8.54441208713535,45.9421799414827 +20.0869535383573,8.80873140793627,47.1975579407301 +20.1397933384902,8.75987471621679,47.7809802273769 +20.0879733062474,8.72601886719167,48.3834912564432 +20.0281300163989,8.78830726583926,46.4454355300492 +20.0144445232167,8.62796103419145,48.9340997028126 +19.8802822913904,8.3246513453419,48.0927049313937 +19.7862705139285,8.31421051367449,47.9501445757891 +19.634302065621,8.39049166825174,48.1563844159583 +19.5196281828781,8.50197502644344,49.0210301808689 +19.4172791610015,8.65418518952319,49.6791400517724 +19.9407755700106,8.47896703021728,48.6410273752883 +19.2860941778694,8.73369792609344,49.3927915688075 +18.5630268516,8.40561348607413,49.6909417768705 +18.3930551635992,8.31758853102135,49.0834627826129 +18.2422342302753,8.28473018579584,48.1298066775769 +19.0201733316701,8.84118414935412,49.9701182589524 +18.0049886541337,8.48258496891387,46.1051177403361 +17.9280909084513,8.66742372417101,46.3490683921879 +18.1206610064303,8.35146966728693,47.3304539237135 +17.8416538597908,8.8469252904692,46.8798132317443 +17.7563733453272,8.93918414879897,47.8115466734844 +17.8103928864119,8.95664837856676,47.760134290784 +17.728040951533,8.94431740787822,48.4184047716105 +17.7123382625997,8.97201599283645,48.636351948785 +17.6578880514861,8.9724998465502,49.6215712736409 +17.5820864574796,8.93545880295558,49.9426842346905 +17.5217041936088,8.96704972649697,49.4098650359639 +17.5123680095058,8.88531218804846,48.8916101300449 +17.5068552534587,8.71287947748668,46.8752189173451 +17.8125201091388,8.96012185328471,48.0410786137428 +18.3892868598887,8.9358796786589,46.1503609665397 +18.4263803638955,8.84564243569158,46.3904363936855 +18.4268187161929,8.49110768084147,47.3068030664799 +18.4469223671404,8.654631368119,46.9534814558466 +18.4314758589926,8.12466658093045,46.9294110070801 +18.430348489073,8.24188245101749,46.8289674675252 +18.4700720723606,7.95606508632436,46.3500993634638 +18.4511814158941,8.02053985783505,46.4126145625178 +18.4929303525402,7.90732495609957,45.9133850278001 +15.9047106308368,7.41241681823723,46.8307983047812 +15.9320731082364,7.63274409453709,45.9252579412715 +15.9203421711462,7.51447607625344,48.5407213807324 +15.9553926593647,7.70956572976893,45.493130982992 +17.4983383276402,8.56609050178517,46.0411096900455 +17.5482434530001,8.40586035722209,45.7625637696895 +17.5184617398535,8.48806448009352,46.1866425970457 +17.5828817003581,8.37760008620629,46.4903994524885 +17.6267347772677,8.4433285793018,48.2788168208768 +17.6473811416296,8.43016488675633,47.5739657565207 +17.6326242010955,8.41512660071346,47.2359109256242 +17.635030385616,8.30393306063929,46.5126016419044 +17.6354772631968,8.34703651465074,47.4102140598912 +17.573171027511,8.45787780777615,46.6658018243044 +17.6103975783799,8.40798955068983,46.9094714187175 +17.633282097787,8.46243477576846,47.9124635169651 +17.5509614908523,8.48403169677404,46.0097105013177 +17.632991138282,8.41450978530373,48.5547947995971 +17.5814628991565,8.47008870055168,46.2099958733053 +18.4747149755585,7.97954821235736,45.3363767358545 +18.4831129834708,8.01274806131224,45.2883377200819 +17.5959233923372,8.43912604156454,48.9144155792504 +17.4989761689229,8.56037765549678,47.1452710176935 +17.5633040171115,8.48312565512305,48.4356438391777 +17.4383617062809,8.67649782642042,45.6160938181669 +17.8027541842614,7.47632101002679,-45.1391041437452 +17.7479217826747,7.49566065398889,-45.1643856859572 +17.9267993884679,7.36983599347368,-45.51453952611 +17.8584880092095,7.40633689343545,-45.0097150293614 +17.9803260519556,7.33773438174695,-45.943072763156 +18.4114124606938,8.46679505893954,45.6251615950296 +17.9979295196893,7.3385047618905,-46.293970663997 +17.9819119284198,7.3402334328298,-48.3078231883787 +18.0158478013734,7.32249970909665,-47.4372727341648 +17.862113712712,7.38863490462911,-48.0758102200259 +17.9152171755319,7.39256570033246,-48.8005354083152 +17.7863680772453,7.46399371712258,-49.109179953859 +18.3629784583159,8.56206888789932,45.8782070078469 +18.2997719218345,8.60183071334611,46.204272047403 +18.2082784654376,8.54816191230305,47.0189120541472 +18.4110201903925,8.54174708554424,45.7637166621782 +18.2003567384969,8.38630208258383,46.3954806904588 +18.1370583120207,8.2894298284694,45.8548097143103 +18.1860854499353,8.47456836587271,47.0632872406811 +17.9425762589389,8.21366367663842,45.2557978079255 +17.9027461717193,8.37591014584751,45.2435981631552 +17.8452583600936,8.40145116378728,46.0280689906422 +17.7817976502356,8.50933630300433,46.4945561470355 +18.2548620987665,8.56873871515918,46.4040943036135 +18.0861043630424,8.20225388731278,45.1999398980622 +16.4831989389361,7.97008963888711,-49.3647920641943 +16.4245525430239,7.93991992481655,-48.5461246867255 +16.2720010065222,7.8377744967616,-48.0008390090251 +16.3319210482795,7.91000029859105,-48.0155235684125 +16.1894370967809,7.77334744568699,-48.3204447772856 +16.2170841398198,7.81036628426655,-48.237689309206 +16.0823186824781,7.65835215112703,-49.8408915244149 +16.05714521585,7.69245946689497,-49.5446050912363 +16.1464097452493,7.71368356221332,-48.7911942450588 +17.7773938476989,8.55094495015001,47.3026817039814 +17.8339451787023,8.45974635878631,46.2268220234308 +17.8262148047488,8.29848933125733,45.3173065078443 +17.7925223357368,8.51916517420591,47.1189243052652 +16.0649582515899,7.74383171784006,-49.2581405708699 +16.0720007808513,7.85259328603763,-48.495264537291 +16.0771349737752,7.94768502014451,-47.0997843763635 +16.0903941540654,8.02764687345243,-47.4826835213078 +16.141838621171,8.13124712498862,-45.8884101477735 +16.1936757679096,8.16295505672717,-45.0727131879916 +18.2639126462918,8.24145440389648,45.7763050908152 +18.1079063072144,8.19961278240879,47.3485458383526 +18.1969293263102,8.20991212048597,46.2804116356774 +17.8916192451505,8.04210108869187,48.003831834145 +18.0014788467777,8.15321755577655,47.6153933427571 +17.6579245566418,7.99910886018843,48.4188314484796 +17.7814722110383,7.96969303288912,48.0460414598058 +17.5429928975363,8.05704566000444,48.436599020767 +17.423331768187,8.10294067961584,47.5442550468205 +17.1598358267649,8.18775688813307,47.1016375285412 +17.3065426514776,8.1633379062539,47.8420919709885 +16.9995747055065,8.35166318727369,47.3479988041003 +16.8222847324622,8.41133008894243,46.8125358477369 +16.6116919464646,8.36283238345904,47.1725032557705 +16.2061288067344,8.22495583560626,46.6789128554474 +16.0014833235518,8.07273842543311,45.4212391295892 +15.8234315306673,8.05094283712573,45.1017393755193 +16.3952345462237,8.26591498064744,47.0598767991682 +15.5794692125894,8.27499710959252,45.1604163274789 +15.4684136765971,8.71402355798286,45.4311023201619 +15.4425609248034,8.77176190997944,45.3972644797067 +15.260252978969,8.7074904367402,45.1549485357537 +15.2253372858882,8.62752614722683,45.0437183759107 +15.1845732744459,8.58793709144229,45.1716178581269 +15.1453417018525,8.58249604780586,45.6110204667369 +15.1229391384001,8.65389734214145,47.608835601021 +15.1716211878272,8.7448961177581,48.6543678019173 +15.229245997447,8.85473092032513,49.4935089701391 +15.4941322223792,8.51097008357304,45.5053674819961 +15.7970655673199,8.23895411152459,45.8873897710467 +15.9224090507855,8.54865393401685,45.1424336859871 +15.7785622323468,8.23885342770845,49.6022851652047 +15.9926140304235,8.64187418394613,46.7178968145703 +16.0542583980022,8.72254957845458,49.6727545363868 +15.6804435515501,7.93486502799046,-47.0506956347193 +15.6834235824349,7.89567506902949,-46.5501144171222 +16.680798298423,8.81257683722108,49.252575630599 +16.6627384836648,8.76928040187053,49.046518795025 +16.6262903935432,8.78315768370208,49.6634374929855 +16.69985004619,8.77046149296762,48.8243193485222 +15.6755499296179,7.8568699561467,-46.5174050157952 +15.6865894219851,7.91859503236726,-46.3672700261497 +15.6762628641992,7.96489854902759,-47.1277293185893 +15.6469783008755,8.04904322006068,-45.220198559089 +16.4256664813666,8.98372622460356,49.0887612488253 +16.2743503469601,8.97384942640653,49.9623573918757 +16.6738487564932,8.93398579945175,49.4808954992156 +16.5936398252917,8.89058570916763,49.7333177197689 +16.805196648611,8.93700297141756,48.7997708830383 +16.5602889458369,7.40147439482987,-48.9160219083154 +16.5770419375338,7.49553963782546,-47.6865373527997 +16.5287787770717,7.74472376187179,-48.0846604604346 +16.5702236743645,7.63500380146286,-47.62810187376 +15.0748427069443,7.93840088997347,-48.8003094880376 +15.0590085849505,7.80063761537965,-47.9052033625705 +15.0075182160247,7.74664978014012,-48.4585661177094 +15.1460324291698,7.95668250896856,-48.164869726917 +16.502730213408,8.02556851458385,-49.3904365674437 +15.8736959921556,8.14912839680537,-48.3698357248824 +16.3335347389879,8.15906971281604,-46.4069316166643 +20.3425228518939,7.74908492719603,46.4866147965797 +20.253874289381,7.66363627289911,47.8215534307083 +20.1437417915386,7.69759523851121,49.0051777165367 +20.0667828554292,7.68102388981329,48.9558996088398 +19.977304151577,7.73451005048398,48.1878026475571 +19.8724614772495,7.76340900454285,47.8135860753089 +19.7952334619385,7.76017817295738,45.5184807685063 +19.742084888139,7.8508661535139,47.76034647431 +19.6175762267333,7.86031352270783,48.2314122400898 +16.0656645780613,8.1394603551476,-49.8070792565368 +16.0269002849707,8.20413299382641,-49.2056053515306 +16.0096857299527,8.40105912957383,-49.2053288777381 +16.1482485646645,8.08274993456267,-49.8236933755063 +15.9869287734304,8.459821064064,-48.8798806744875 +16.0327847239882,8.27246381061001,-48.9682351540261 +19.4477257571351,7.89512426733118,47.7085499297701 +19.2431375668565,8.04257985662035,48.6001200760045 +19.2535198536489,8.25246873003491,46.5751305939754 +19.3598280560892,8.37087128141492,46.6794594493699 +19.477688440989,8.40389147990699,46.1916927297798 +19.3288715375179,7.94289125178435,49.1574559382512 +19.5268227080549,8.43027207713338,45.7500744730979 +19.2154076772099,8.19028832357444,48.0567747428821 +19.5646227844206,8.37511236122666,45.6761494051917 +16.2079316688642,9.04026929122764,49.9606781812107 +16.2662381340943,9.0641004915141,49.9801669237156 +16.1584869476814,8.84230230292325,48.6933872282522 +16.1066352384352,8.76030244405859,48.2789109198076 +16.0771211364318,8.51392314077665,47.9708245960451 +18.8810821674622,8.33049527153574,49.9133873312242 +18.8832863434515,8.19343711989806,49.6835459070015 +16.0813195848859,8.3081301206583,48.0746954993413 +16.1273702733631,7.96798096135968,46.6484540059639 +16.1017693674759,8.11734215037957,47.6973248285319 +16.1770257723377,8.00674838525582,47.2176061749841 +16.3853755009286,7.99838752727651,46.9293134638126 +16.4447171006832,8.0357730910063,47.4880262621344 +16.5227363801334,8.00979851190306,47.6331596281904 +16.3271450744164,8.02105961905954,47.3532499206676 +16.6560728559403,8.00053785456099,48.3898505390469 +16.5947383586835,7.97283977767709,48.0639502296082 +16.2509563393037,8.02198772311721,47.3418118448776 +16.8368877662462,8.55703732635479,48.8256572301414 +16.872186627145,8.74792489545508,48.5833794112118 +16.7301340415883,8.08674227218989,48.9412135772882 +19.9181457260429,8.08520566306399,45.8415707090905 +19.9174641889096,8.1777775142299,46.2555984198642 +19.9057705178909,8.23574714382618,47.838542089206 +19.9360093500836,8.35899518054604,48.8758000555482 +19.940239441612,8.45590807980786,49.7568602478792 +16.7806687884697,8.27367627842042,48.8368886783427 +16.566147277282,8.22361239422475,-49.2007751977881 +16.9167933659381,8.91480955121546,47.4174763285259 +16.9136073914526,9.03148512161224,47.8171953204251 +16.3229983723729,8.27267954774363,48.4992183534598 +16.2484558605087,8.2861783777018,46.3935362595407 +16.9175276325967,8.99495273021989,48.9772125791944 +16.9266328063494,9.05212726196756,47.640996421733 +19.9686273990451,8.7128390049789,47.0774157715296 +19.9538046604123,8.68629404554592,46.7954844836202 +19.9842764100574,8.61449128668371,48.5665011583778 +19.9993801029275,8.63681250354965,45.3723709863442 +16.0892796737787,8.49714301842202,-49.5905743621223 +16.0785832099414,8.21442190338103,-48.7770477752998 +16.0716495269998,8.29831247689345,-48.7691523761081 +16.0292950893249,8.12703078465161,-46.7470507865189 +16.039852509334,8.16956227124001,-47.0268266518549 +16.389721131521,7.72943274079986,-45.0601662451005 +16.4646209070498,7.56221510388234,-45.9707999612804 +16.6279083285734,7.57415141117629,-48.7313218808955 +19.3969202741191,8.05819731487172,46.5812769030949 +19.4661813736682,7.905354774997,45.3242413927823 +19.347589819061,8.2578081457765,47.454717764417 +19.0601841384313,7.86191248615621,45.6243088473578 +19.1194991129106,8.00070184158793,48.15065984849 +15.4525115648524,7.31438615482851,-49.7672266482008 +15.4884799258321,7.24258163466743,-47.1288180589418 +15.5419922397213,7.29874500478202,-45.7220615593745 +18.5478439816718,8.31376870049765,45.8540028334886 +18.4112760052322,8.30288387982641,47.5697634890136 +18.3295556590659,8.36062701083863,49.6147187755768 +18.2627109034358,8.27728307770954,49.6732376325293 +18.0658898827803,8.21331604431792,46.9346004063455 +18.1746870564861,8.24913405202894,49.9569935150691 +17.971427427084,8.23829394057504,47.6198443251426 +17.9092847328971,8.23594884247415,46.1151455528592 +17.8454109880691,8.26703962938343,49.0522097479876 +17.7530076931549,8.24817752161144,48.278210252112 +17.7123469601324,8.28083275374343,48.1051820075185 +17.6631602418772,8.29134213457888,46.9917984282609 +16.8797401519065,9.198235062542,48.4973901182427 +17.1935833501822,8.01838209283811,-49.9343394513917 +17.156899380679,7.96639204872268,-47.987210401029 +17.1051868920409,7.99953694915163,-46.2581248726091 +17.6506340532033,8.31998712000826,46.790716447343 +17.6939017775066,8.34080452649834,46.6730734292666 +17.7494895268922,8.30334202487851,45.9381599606248 +17.8053007768342,8.30366799595904,45.0019135286703 +15.7722524470915,7.65347980038984,-46.1045067846857 +17.9540387554201,8.3339017848885,45.1332995804049 +15.9033527672075,7.61039409662361,-48.339248214761 +15.8605684784263,7.63085413284928,-46.8115108517316 +16.9416780764063,9.12220519967307,45.990658421509 +17.176722560924,9.0445932316442,45.0744979196526 +17.2826777450408,8.95769605991696,45.443194922578 +17.3636512887063,8.89384830027189,46.2773815732632 +17.4532816819448,8.82715181464257,47.3001169961419 +17.5550168939191,8.91146904348198,49.6330205609786 +19.4445453874892,8.27351914978644,45.2971111948746 +19.5279237700264,8.36751879628844,46.285721653896 +19.5300919888961,8.34531763337842,47.5944830804915 +19.4629163299022,8.26432912960487,49.3541478591898 +17.4532375570575,9.02756047599212,49.1446959505286 +17.4024948195351,9.06737077984737,48.437875308505 +17.3885711986333,9.15850262556279,48.7397978711764 +17.3673243534916,9.16919268327783,48.0012175849084 +17.3424649801714,9.23054964271913,48.7181441355463 +17.3208302748841,9.17203594311716,48.544568052211 +17.2663696516227,9.12355527478481,49.0049889720788 +17.2533409131672,9.0498122893019,48.6151114336978 +17.2629935825062,9.03546307065151,47.6438232300589 +17.3357284092047,9.00464924090881,46.2837483226592 +17.3750801269599,9.05130062210208,45.1271621653998 +17.3483652939299,9.25288726152701,46.3708185588522 +17.3250097512623,9.34039612870377,48.2674418092673 +19.473798371525,8.36775676280863,45.9286223698921 +17.2871329872737,9.37277144690786,49.4356857281031 +14.5152463149271,7.70833857089158,-49.9262397789147 +14.6585652649853,7.53304750874344,-49.2716903912843 +16.2540236750308,7.86904576931289,-46.7718100771252 +16.3303886951216,7.87100341302853,-48.0526425119606 +16.3924014109682,7.96532280588651,-47.9341888679128 +16.4920877486875,8.05616564630753,-47.6503190716626 +16.6052938481658,8.11486989644045,-47.7886808086129 +16.224507557276,7.85830798838767,-45.1356445960868 +16.7425218841766,8.19825143324441,-46.439663132237 +16.6694220172168,8.16149206437554,-46.8557471126209 +16.9470872381604,8.23848872840488,-46.7431759232547 +16.8358683728841,8.25321678379346,-47.1416994597584 +17.2191940104187,8.27414311220275,-46.6919865618154 +17.0829979903322,8.22847355704276,-46.8929009573048 +17.4604093546702,8.20149921521129,-46.6958524981911 +17.349818645312,8.22195977384716,-47.1489962530063 +19.7530416705809,8.13394123403205,48.0327565572485 +19.7159550531173,8.13674252611635,49.7592103765823 +19.8100522841521,8.27577635540142,47.1971304256551 +19.7666548517116,8.24605013178871,48.0160855457092 +19.8582513911062,8.41366339389889,45.8946361728969 +17.5696876044425,7.92590050391477,-47.4135834770904 +17.5958407356311,7.71073783633976,-47.9999626442886 +17.5190988035163,8.06823447784211,-47.024434897304 +17.5720304009215,7.62230385262976,-49.8388225221487 +17.5873938068904,7.63340400210649,-48.5217918746414 +17.0851732426172,8.62398667888822,-49.7295104770685 +16.9922443397082,8.68247984881593,-48.5894292095886 +16.7662382133791,8.44923387341379,-46.5679871730602 +16.8808835686454,8.5426623373651,-47.6484034222519 +14.8295882630976,7.45782230136268,-49.7914976221501 +14.8289440861658,6.78601752646684,-49.1273479812387 +14.7444651253643,6.86040067367077,-48.7720813787336 +14.6514381487753,7.02559688146512,-47.7125129198494 +14.5714147028537,7.34815214053925,-48.0149396090813 +14.5927219616421,7.1989774185713,-47.696359828522 +14.5307494742169,7.37995358446498,-49.3850898610479 +16.6621776158655,8.34386370459164,-46.4504342489841 +16.4123882381022,8.15009203103992,-45.3305521196058 +16.2584090025998,8.13357847022382,-45.1606253351093 +15.9553648558604,8.0287063476566,-45.1690961809371 +15.9420907720023,8.03914407538061,-45.6414545619542 +15.9509866549137,8.04202546992139,-46.8175402663578 +16.0029743680176,8.11309112007218,-47.4656523676499 +16.039529607462,8.18237195201837,-49.9779787201982 +16.543944733133,8.20645599285293,-45.8206507570313 +13.6824207327332,7.22558335015151,-47.9809133287434 +13.7610569941551,7.28597622960442,-48.237084281538 +13.8169327529148,7.40845288382725,-48.6967839356338 +13.8567435020645,7.51655002451873,-48.1057529481586 +13.8824930569725,7.4975729611008,-48.6168678986508 +13.9101254817064,7.53581838315872,-49.3296460274612 +13.9244898418546,7.4513292566485,-49.4117390450095 +14.0283372391667,7.41329801527575,-49.5044346110511 +13.6379126990056,7.22071136953221,-49.5686328131064 +14.312438408331,7.49138398672795,-49.2807173238851 +14.1242982398174,7.47414408342508,-49.3558545835256 +14.2307582429153,7.41330751818035,-49.1047981511909 +14.4997486895997,7.51861422312716,-49.7999975505965 +14.4113380567899,7.5048447236673,-49.9088855886915 +14.5092434548259,7.275115573601,-49.2991149759862 +14.4362975630348,7.29117356780819,-47.4305459912983 +14.4064112225225,7.35173900371104,-46.492199736757 +14.4124254411392,7.29463815136703,-46.6696340732009 +14.4648286341662,7.27123334941092,-48.1086193194103 +16.9055986029944,5.16909820107218,45.4053095990895 +16.8160452692888,5.50671945456937,48.3131381626235 +16.6984409278841,6.04061390359495,47.5111369380127 +16.572892603515,6.12980784134575,45.5648597333011 +16.2293382308676,7.16493609638307,45.0066978471411 +16.1289950086046,7.39491781690806,46.4591141913957 +16.0820551268454,7.36659594562887,48.6710530465545 +16.8911940975399,5.32065066304064,46.6746773841458 +14.4303972819234,7.39044654688747,-46.7319276696501 +16.7480671720434,5.68796998365359,47.6767051175194 +14.4215755096518,7.45072138045247,-47.7183562553492 +14.3591737656867,7.46469276768734,-47.6479986752728 +14.4223189498349,7.51613828484847,-47.7565300190604 +14.3215071007088,7.43539353159243,-47.2836959446581 +14.4155834133646,7.46717331408206,-48.0122193788347 +14.2867575194397,7.41367054591352,-48.2033758449226 +14.2294840499903,7.28548269177788,-49.1764651705842 +15.6308845802348,7.38195212902659,48.6369056027742 +15.4919490680658,7.37328181670437,48.7129976045033 +15.5363964441307,7.3815063364715,47.9423399845476 +15.4308638762093,7.44673538765882,49.324550655197 +13.9644645877098,6.92919889509232,-46.4466621137359 +14.1908385121951,6.77511292728198,-45.1602460821412 +14.2218022425909,6.77528456947266,-46.2769170846414 +14.2860278052689,6.81490676912488,-48.2334986624174 +14.3378480382516,6.9004013405499,-49.3761971603055 +13.9797226813812,6.9596437413681,-49.2593280437287 +14.3835520117005,7.07664486961797,-48.792254693847 +14.4257430106215,7.04364038880021,-49.4981143338173 +14.343999215941,7.13477713663952,-48.2323657664401 +14.2331488411006,7.11659982040326,-49.076884932391 +14.2049235017833,7.09182132527626,-48.449053688798 +14.2106453063332,7.14248851126158,-48.7747451299907 +14.1671852753132,7.18005515968829,-48.2351663076531 +14.1319359793518,7.24663540296533,-49.2082299953083 +14.3079921358406,7.15710059533442,-49.3258019729515 +16.5459331135468,7.9693416262442,-45.0223123988613 +16.6423409660982,8.01503443708282,-47.5827305823167 +16.7559777967081,7.99650168629226,-48.653885850884 +16.8860764310407,8.07235978286426,-49.7453728750679 +16.9727050821638,8.01517251649586,-49.7740893353882 +13.762416423623,7.16813662078291,-49.8317917156482 +13.7180285862519,7.14774452341498,-46.9946346185832 +13.7409245380296,7.33323555218461,-46.5859109339818 +13.7554315387248,7.13746852701516,-47.7893850759459 +13.6950488889829,7.24166947874699,-46.9095322233069 +13.6774296515441,7.19343566412882,-47.4142540656739 +13.6685996657709,7.09425314036035,-47.7476166669386 +13.7054655622393,7.24326743816254,-46.4236665516457 +13.7280995658975,7.29130616529143,-46.506961613887 +13.6657005274943,7.05224285142226,-47.7589776289454 +13.6441062088011,7.0138720318386,-47.552377347013 +13.6051862841848,7.1309449991849,-47.8172555867156 +13.5912883231079,7.17698175622552,-47.2453915178373 +13.5935267674103,7.29496409765609,-46.6377261465632 +13.600256826507,7.49055380096144,-45.7330986495743 +13.6262464649984,7.56007061698703,-45.4436578028453 +13.6551058447227,7.56265075972896,-45.3088645022959 +13.6988379341746,7.49477035770534,-45.2045225444645 +13.6965772528864,7.4308692713562,-45.6654902092315 +13.6827657625617,7.27247297576978,-45.487814581968 +13.6390165321859,7.22520753471956,-45.7951420132713 +13.5412728875595,7.15853917786174,-46.5783607912059 +13.5766936711963,7.19298457361705,-46.4669146175716 +13.5745882037854,7.48161284418859,-46.8028853616499 +13.5024489134972,7.17092329556179,-47.199532559274 +13.4149802461134,7.23115406199579,-47.6563779979687 +13.4230819807655,7.18703744098021,-47.1567313389868 +13.4427518711272,7.17861784034068,-46.9435845973501 +13.4216919571526,7.17660790382332,-46.4629719562766 +13.40866454087,7.19769944348622,-46.6768150946682 +13.4427877119947,7.11071461522656,-45.169990222682 +13.307613301645,7.32853284022861,-45.3580137510409 +13.2885877305476,7.36443914655576,-47.662896902318 +13.3321977318001,7.28349424218773,-47.9866637346877 +13.4430372647864,7.16106109643428,-46.2034481517806 +18.1323120850459,7.38912575095998,-48.6728111456842 +18.1996001055341,7.4075456072702,-46.3217235085711 +18.260923078804,7.5668564447049,-45.9872291764078 +18.2951946715635,7.82809998658374,-45.2607287241282 +18.3161261707447,7.99917194264878,-46.1780222005235 +18.2882599562238,8.17561956432378,-46.8665323979858 +18.1872698063245,8.32137622209354,-48.3052785147668 +18.1091805752959,8.40788211517305,-48.6107473777078 +18.0412025228265,8.50873993802856,-48.8338279178892 +17.9711582226913,8.47329505163486,-49.8203424645389 +13.2817985270669,7.40862015206171,-46.7269366115221 +15.6604709737091,7.69532887534046,-45.3257062019168 +15.6523815967316,7.71902059236066,-45.6454056499394 +15.6851078018002,7.7903047520149,-46.8786609880238 +15.6828813099932,7.75039282577766,-47.4244182008568 +15.6574465659248,7.73846547858214,-47.6088316426768 +15.6602693843392,7.61877327103378,-48.1915213824064 +13.3772810101616,7.20606608022411,-48.5890424134523 +13.4065192426163,7.13380515672027,-48.9666961265412 +13.4020098677854,7.10207255935279,-48.4940473467693 +13.3727160940769,7.034679308742,-47.7089702014554 +13.3605729779919,7.00478083677868,-46.9338799057382 +13.3633132138223,6.92692951725655,-46.5368798071804 +13.351709718882,6.83181673773051,-45.5748325076903 +15.6790816994035,7.56502715146827,-48.7911798622883 +15.7684303877596,7.43010283014469,-48.4420291570264 +15.7370254200745,7.42773780698135,-48.1838676050074 +15.8105655039006,7.49096068905701,-49.115560643481 +15.8751462233261,7.6214477952077,-49.0841670762216 +15.8624156012279,7.52613694127145,-48.4474262148801 +15.8967616075855,7.64514674693363,-46.4745049766091 +15.8721632324109,7.63534595084707,-47.0653685321755 +15.930326957992,7.54672587935512,-45.4243235632513 +15.905464633362,7.62632263302434,-46.5451402731003 +13.6641346672012,6.78838199388611,-46.7315840163235 +13.6934840756041,6.78106583280436,-45.4542145703581 +13.7330234309272,6.80485570866012,-45.0141709827769 +13.5962840534196,7.28344236973851,-46.8561147196567 +15.611140859732,7.36760437191858,-45.9492680184614 +15.4939415138062,7.25506750946381,-46.8843347287905 +15.3899117891519,7.17001266371374,-46.718326143124 +15.2965644341945,7.11228478315205,-46.4254342828952 +15.2016932375048,7.14675898912393,-46.8281898519464 +15.1144040411089,7.23508020509379,-46.454575888718 +14.9859825892554,7.28979031182201,-45.8914574871351 +14.7497040266392,7.45409307026195,-45.9634481786266 +14.6090783497964,7.45019785039736,-47.4992935771916 +14.467295302488,7.45053402639058,-47.9504985584511 +14.3483019671399,7.47331525331039,-48.4441288774149 +14.2505210910299,7.50539124971065,-48.4174579719165 +13.1556442648645,6.69980347944503,-49.0302724165118 +13.1311991151684,6.86131708338273,-47.6612873157661 +13.1035235768529,7.00356915573426,-46.5749550876305 +13.0845701662178,7.19779080633143,-46.3753466162285 +13.0274171744932,7.33726888684831,-46.9369464197255 +12.997608913662,7.45894760770347,-48.5861384593345 +12.8894271035322,7.45309674384349,-48.8071657782909 +13.6783800507494,6.81862148709482,-45.5157524538458 +13.5066784375949,7.31172269462232,-49.313751455294 +12.7831251551238,7.32993900909728,-49.0102029795637 +12.7824387441197,7.29682326819017,-47.7117303432114 +12.7971170091335,7.37452301251697,-45.519780454963 +12.7877815243705,7.29168570713576,-45.9277773361165 +12.7326064061938,7.47915284081707,-46.1711493354796 +12.6697788688196,7.63485736051317,-47.6734907805628 +12.5940685626093,7.71935890169785,-48.6398866099707 +13.8614566323547,7.68983149753668,-49.4882100995959 +13.8654528938165,7.68087014564921,-48.9995089319197 +13.8936823316473,7.70880396879147,-49.0642542688758 +13.9126998579306,7.69660939655977,-49.7003189636712 +17.0977981262189,7.61939966028785,-49.7936963718291 +16.9186981375874,7.83393694402024,-47.8710477639031 +17.0019177688075,7.68993137349646,-48.4012940017946 +16.8646743945392,8.02828838908727,-46.5257609650652 +16.8731926338057,7.91171295116231,-46.58338701746 +16.9257677288389,8.08457766960775,-48.375195563536 +16.8905080159577,8.12054007977912,-47.4363388328396 +16.873600324119,8.00825259239312,-49.1245251079279 +16.874703595895,7.75569294062085,-49.4356186774005 +16.9164276307379,7.64904393471381,-48.163718108094 +16.8784381123082,7.70242562144507,-49.4387329952686 +16.9676412041335,7.74100808681299,-46.6861701737746 +16.9253072003542,7.64451207898217,-47.5881862316098 +17.0904175651058,7.92276041556379,-45.6838553987465 +17.0516621755396,7.87367500838817,-46.157240514919 +17.1202150744289,8.11577895576003,-45.4335309042783 +17.1187245588545,8.05783191719356,-45.1603953375928 +17.1558898318208,8.31583876055524,-45.6693699997731 +17.1318605506212,8.20217037615532,-45.5422756835127 +17.2155065197497,8.62981138203739,-46.5766830218541 +17.1988404241866,8.40221995390179,-46.1031965760056 +12.2325957650051,8.10958050206235,-49.263686102565 +12.1935486067819,8.01618283313575,-49.1173824553881 +17.2340664790437,9.15168551625476,-48.1375563442243 +17.2407771224467,9.01432323989444,-47.5308922584274 +17.2392661902809,8.84519745847805,-46.8401684760883 +17.2983521476062,9.06901980538144,-48.7422516230677 +17.2716166689337,9.14870653070988,-48.0302229706685 +17.3674311038711,8.66940854110823,-48.2618043743304 +17.3237869993654,8.84938350923582,-48.6493678704346 +17.3849479203073,8.32142823832326,-46.9926913362172 +17.3610718928948,8.48080083560694,-48.3091457475674 +17.4492692511345,8.43992899827552,-46.4981563543735 +17.4462554146306,8.35840312402565,-46.1021266777966 +17.4997323671548,8.73271575719053,-48.8802301326876 +17.472862413344,8.59055921923092,-46.9591657845897 +17.5270186932851,8.95322308121857,-49.3511716617435 +17.4053320624048,8.3276041920424,-48.4103183717326 +17.3769815357105,8.15193486024167,-47.3169294199641 +17.3200155972677,8.2053631168696,-46.9639945873907 +17.2430521788623,8.28636669678642,-47.0147953073364 +17.1312537514,8.48564758465168,-48.1897136312767 +17.0488593742555,8.60363887988422,-48.8989148215583 +16.9306540731658,8.66518897138803,-49.4027756497099 +16.3319965023717,8.5551162174234,-49.8367149856591 +16.2573830349162,8.44037415851889,-49.9523587130835 +15.9489538798471,8.31546069338245,-49.5543880382983 +15.9938354406008,8.17267140877621,-49.6906094095787 +15.8391077640779,8.39196630722624,-49.5120240022793 +13.7249981278186,7.54014553081595,45.3103651453161 +13.8003668933934,8.00495861748758,45.0677930445624 +13.7433018223023,7.43770544658978,45.025343452803 +15.5403060725352,8.43871734232648,-49.9472826037888 +15.6956273797943,9.20595168885075,-49.9512884932969 +15.642446125456,9.02537826486912,-49.1960998719828 +15.7344841640927,8.84126880846996,-49.54062483907 +15.7703941896841,8.92163880808155,-48.6978294669146 +15.8132232313244,8.98792012640262,-48.8193939339454 +15.7978517997408,9.03686490806899,-47.7519817965324 +15.7888523429325,9.04262761698786,-46.35753757433 +15.7383456840538,9.09950816278809,-46.0852594595863 +15.6626032700248,9.14889007658915,-45.3782633458533 +15.5635400329378,9.280809074948,-46.1099885912152 +15.6850638931396,8.62172315922823,-46.3386542803691 +15.6497095016383,9.26379281388208,-45.4207160602904 +15.5833847361962,9.33906860413671,-45.7093314906126 +16.135114391348,8.59207443916266,-45.3706868182651 +16.1066229899289,8.64525950688996,-46.8513866119526 +16.061671149284,8.64379767834831,-47.4797180715708 +16.0125824426554,8.70979380595116,-48.1498720625596 +15.9942654077049,8.71829912336346,-48.6619957023079 +15.7531311845125,9.24420302358174,-45.2441424967027 +16.0122660635597,8.74412700618618,-48.6149510331952 +16.052361619143,8.74143466887986,-48.9763539247172 +13.4131745345466,7.03853455274328,45.0479597269602 +13.4506191907386,6.92217306585451,46.2332391940872 +13.4842016663208,6.81760369961657,47.4557533009304 +13.5609804033926,6.59311286315777,48.3788539059038 +13.672374365986,6.44846139245875,48.9964286301829 +13.7928404597358,6.24576774696491,49.0234761141929 +13.8592274100698,6.23711067803242,49.8726276496045 +13.9484777675464,6.31261314886773,48.2919512914546 +14.019363810259,6.36406820077875,46.8639474553265 +16.1255251257402,8.69628422857021,-49.9705758483219 +14.0513463666586,6.39597007648705,45.0855850737365 +14.0693693917768,6.60591620652658,45.0197003635582 +13.9456857073846,7.18338964565722,45.2840804059272 +13.9375000990729,7.37678240124976,46.415218417056 +13.895199896461,7.54436405931633,46.4779466032813 +13.8750872773231,7.56926394365153,45.465827394531 +13.9326038181765,7.46560114029047,46.0474750735777 +13.7986392750246,6.96777298555085,45.253239892293 +13.7521674331906,6.83560280732041,45.4119306404131 +13.725393936516,6.94272593222388,45.8081884845569 +13.6886104374542,6.94601238262333,45.7871131240429 +13.6846523240205,7.03798217933146,46.1572992001139 +13.6634273425099,7.167076335453,47.4054629456489 +13.6591288122688,7.1829623802991,47.7420937600335 +15.8835954501569,9.20964883392958,-49.4302935574884 +15.838186510533,9.31993207924723,-48.9472749824676 +15.3023211020238,9.09439194727135,-49.3567955480702 +13.7710400858296,6.903523610629,45.4077990385749 +13.5832288744131,7.10162046942168,48.3291835289498 +13.5786845842154,7.13123173340253,48.0537299064165 +13.5606395725101,6.99006534049577,47.5419590717647 +13.1926735112196,6.5190838281198,45.210109574239 +13.1186621174648,6.5316440884833,49.2345492279518 +13.049770193909,6.52031194363907,49.9174095510953 +13.147429330551,6.55343993945104,47.6815863953771 +13.6225949613486,7.16853261350319,48.0251939482134 +13.526860845527,6.82951773287042,46.4067937461078 +15.0303339931148,8.91676741006614,-46.0378513519722 +14.9506270337445,8.9174619601255,-46.1302397331234 +14.8647762652566,8.99697460143399,-48.2793530859153 +14.8765271796966,9.11010093795544,-49.0077389837259 +15.1630787113832,8.97394024565833,-47.5464983360416 +14.8812052404421,8.95281878634084,-47.6743132376598 +12.9525569944217,6.70664371901736,49.3957348972564 +13.1259444018103,6.68801486158748,49.775340761573 +13.2244687384879,6.66058895184396,48.7024759222418 +13.2825648163219,6.69108296350478,47.7208298266241 +13.3400661766877,6.83755867611469,47.0369487183851 +13.3942164226575,6.89597800760855,46.7913552739798 +13.4314596395481,7.0443408750392,46.0167828299514 +13.4593243212438,7.01408404685516,45.2747172491035 +13.4548556716625,6.50254438844213,45.8604197143038 +13.4556417194533,6.48025302724282,46.1042078031469 +13.3368674328876,6.45773737121586,46.6000133293062 +13.0422413576936,6.66897866963649,49.7213909797416 +13.3257332737221,6.32962179496639,47.072430266639 +13.326938936065,6.4358641690315,46.0953368040933 +16.568811900089,9.33743946632982,-48.6119352461548 +13.3256057123333,6.5140288974107,46.9090709365586 +13.3631011892762,6.53104992675977,45.3415882495808 +16.2434498584086,9.25335979384795,-47.139667643529 +15.8321384365695,9.86314047352901,-47.2813786286208 +15.7466788441509,9.97025265287789,-46.4878417989281 +15.6854159173853,9.95558204453272,-46.0603498613325 +15.6683567847332,10.0136779926521,-46.0243396448227 +15.6813894121424,10.1725089366819,-47.7158201667766 +13.169516514141,7.12002230085227,45.6055003435608 +13.1581615351879,7.13841160046354,46.110831425511 +13.128720908351,7.18662469600234,46.9344266675975 +13.0915704051509,7.16320355804575,47.1030179921904 +13.0887644071795,7.18830285746909,47.2061549746559 +13.1078060957495,7.20954250267248,46.3020845428881 +13.159845016912,7.21372243141256,45.0799514280494 +15.7793837413996,9.67188518079411,-48.1965145258642 +15.8708040291057,9.4245266647687,-47.7091687714962 +15.9049296924938,9.22270301478579,-47.7831469775861 +15.9310739070607,8.87409594657482,-46.1301388565767 +15.8880756328718,8.75290595124093,-45.1967240910568 +15.9135009791503,8.7176271621664,-45.5725411947069 +15.9463361496068,8.69994001247719,-46.9699807719728 +15.9147917086522,8.63370784862518,-48.0168376821307 +15.8147580187195,8.56621103776366,-48.3911542392203 +15.671717614048,8.52568406532781,-49.1836595784539 +13.3263954698174,7.37536649121791,45.2226898426624 +13.2994178387962,7.38178389057206,46.6634375086078 +13.2972593952145,7.26221206843212,46.1888342373627 +13.3240347287898,7.09473018090142,46.4190141875243 +13.3585910320814,7.00745580068564,47.2044247337782 +13.3579651506655,6.92474469456493,47.057836855816 +13.3630030723168,6.86074620180082,46.3830640718722 +13.3805282914164,6.83569824514896,45.298372496606 +15.6999506253477,10.1942886452947,-49.3776566810895 +13.9198849947897,7.1752236810857,45.4617264874311 +13.9642096044092,7.25949764131279,46.2896738298512 +14.0104401230154,7.26795277642344,46.562562622753 +14.069165988227,7.26464535414839,46.8574919932888 +14.1373703025123,7.25965802716427,46.7384059721189 +14.2081218120225,7.20114332819311,46.710935767827 +14.2679337681222,7.0691365737944,45.5781400724819 +15.5527096356971,8.50597596533297,-48.2955258744997 +15.4438620630668,8.47605859690891,-47.4280475402041 +15.4166154479259,8.46549471018214,-45.7068894927858 +15.3938483725034,8.38350380346031,-45.2193057132903 +15.4223809520858,8.19852411868616,-45.3796092737193 +15.377574665838,8.23604336021592,-45.3655928122708 +15.4843768094137,8.2345102778078,-45.7595223590061 +15.5593962515679,8.27765573070186,-46.3657985399913 +15.6552709005308,8.40483803641765,-45.3501148988202 +15.6940754678914,8.50167140702227,-45.7681904333045 +15.6945592354342,8.60128041238046,-45.1481845823999 +15.6753507567796,8.6494018667191,-45.0463116495477 +15.6620710203631,8.6897907766487,-45.4905985591357 +14.5755819154975,6.32615118409827,45.5701263978948 +14.55923290186,6.47738913046932,45.4909451379124 +14.610172109474,6.14553238492898,45.7780915288835 +15.6430375129619,8.77573734354624,-45.6273595246856 +15.6562774614274,8.75305218903122,-46.073959120191 +15.7068284825815,8.72439497406914,-47.7247365686804 +15.7091701444082,8.78574498677863,-47.2953960439564 +15.6677604107391,8.75134212616766,-48.6352005903629 +15.6844749446753,8.78774786350699,-47.8634864969518 +15.5355719240341,8.80700010366007,-49.7879836502126 +15.6006581779556,8.80749360945374,-47.801945566834 +15.4580434499344,8.79557347764866,-47.7453089739527 +15.3212498433176,8.79844572576698,-48.6027755948521 +15.3505133123819,8.78940275787676,-48.7862911788957 +16.0680175011233,7.33668828684233,45.7392416366139 +16.1398702200372,7.44020895599296,47.4358322187868 +16.3012022321744,7.5329296847514,47.3791291343828 +16.4100784267691,7.65165140472771,48.8807617269201 +16.669467395403,7.78830040527791,49.8341094373571 +17.3956969841698,8.5651348271496,49.3442830115632 +17.5817116220487,8.73354827897636,47.3371784479226 +17.4958766555999,8.67129137964225,48.3361113958 +17.6974854375314,8.59762402262477,45.7481872585851 +17.6498693900699,8.7011059348226,46.4249465656722 +17.683463330273,7.85189879871137,46.9413817400047 +17.6045844498112,7.99381185369197,48.317447840782 +17.541487155243,8.04757832115619,48.9486475176768 +17.4851442279941,7.98019899124287,48.2264266359621 +17.441482291704,7.98979827357812,46.6163823043015 +17.4266020859604,8.07089213492536,46.0296846975107 +17.3814527294303,7.99410104372827,46.7057412378315 +17.3200016784078,7.7871608204787,46.1189881694308 +17.2485963809827,7.73806906720017,46.4009233672334 +17.2283911747521,7.74366569000731,47.8103546257154 +17.1822640250817,7.73045106670144,48.5416290976909 +17.1890287896558,7.39669791790494,47.4329814590324 +17.1950868979164,7.56949978317347,47.6890047282854 +17.1909057349343,7.51762586504292,47.6233899589915 +17.2004304652621,7.44137509280554,48.5942944824502 +17.168786417982,7.20193507432934,48.6090729707903 +17.1062151834018,7.0742070203931,47.9811350785337 +17.0441367090924,7.18810885415514,46.106125744543 +16.98521560773,7.25800283871209,47.1673534202218 +16.9486573287057,7.33189864316941,45.3785805280222 +15.5683558253408,7.43406571387531,-48.3100570006458 +15.6538388257996,7.55601952354766,-47.1296208257937 +15.7336562221154,7.63829369803554,-45.6069848254034 +15.6403511090779,8.55266312685269,-45.7604320644023 +15.6698762695501,8.73359863748211,-45.939774550137 +15.7700196782198,8.87193540375577,-46.417408223573 +15.841446292427,8.9588504472429,-47.1421330886536 +15.9927115890903,8.99704475022203,-45.1925030029341 +15.8997348725541,8.95515715823805,-46.3172261282678 +16.0764046438527,9.29977294231221,-45.5914935157644 +16.1423476897208,9.43420378199723,-46.1904683437113 +16.3100407156782,9.56086052123673,-47.2059918912915 +16.2410788776831,9.53436925131606,-46.29462810371 +16.3494913067955,9.57210257917205,-46.7529515917638 +16.4010366669017,9.69575356665203,-46.005934209599 +16.5199935551667,10.7111920892384,-49.2860194647373 +16.895255323367,7.48957332204299,49.583659042677 +16.969216250343,7.60336075269833,48.1766017372199 +17.0825099872087,7.7440181502613,47.5393318721624 +17.1987027285667,7.79203213721709,47.2840263812293 +17.3596766712808,7.86918509964783,46.017999979471 +17.4717100375692,8.052295849063,47.297641828382 +17.5318137211552,8.10642215923022,47.2882027654408 +17.5272573869155,8.10891712156561,48.1758250450089 +17.4164012227467,7.93215063482683,46.4711881263143 +17.390551020648,8.45740342002474,48.2217090318222 +17.3241384192077,8.51362047926148,47.9625674138899 +17.3038032102396,8.5792600860445,48.2380832030692 +17.2781681273245,8.53938288846864,49.9474593054274 +17.9172018167442,8.13085038778434,49.5201601851736 +17.9773779006975,8.07724248767601,49.8633622159659 +18.0592421014998,7.95824546025289,49.2979355256356 +18.1282312812138,7.972365231216,49.2576648281457 +18.2260562840485,8.00174524929283,48.9752663773568 +18.3136110173636,7.99507516767022,49.9735713674544 +18.7884945052073,8.18776373866586,49.689151159681 +18.6529616018851,8.09958566949621,49.7202660679358 +18.9155359727303,8.2529395401534,47.7169880913458 +19.1121102896085,8.38657811955576,48.5482942490053 +19.2905435545944,8.4691837184773,48.9489869589742 +19.4104406518683,8.4846572710808,48.746595729274 +19.5028913822593,8.4382832077338,48.6904115672814 +19.7757174510575,8.24029868490526,49.6632327742113 +19.7800145732818,8.15021654667233,48.1725967592309 +19.759892806001,8.06578184469915,46.5018920741537 +19.5464154029663,8.19855095974432,49.683739968141 +19.4347475348693,8.31056598285822,46.8037154310761 +19.2799481509611,8.35317239575691,46.9945131546928 +18.9563675377326,8.24224171374336,46.537305845712 +18.8871110114337,8.20662273582606,47.7462825400969 +18.7760710908452,8.0735746721705,45.4156634200552 +18.6190816060157,8.0756020480269,47.5487854069645 +18.3361268080851,8.02194035846529,47.4464217985004 +18.1824409991114,8.07306050246521,48.0577970368889 +18.0582823470546,8.14614431443226,48.060687437808 +18.4625522200593,7.9901786121869,47.6018711038815 +17.9507975141743,8.30535452176195,47.9556282082241 +17.8752926865682,8.48062812221878,48.6115509697408 +17.8757464876099,8.68802351258955,47.8035064918192 +17.9084932861929,8.82811732942104,47.6405101528008 +17.9785672127761,8.93340252918031,46.634779844272 +18.0343479972047,9.01029424126541,47.1169537254964 +18.1587064362402,8.280030617049,45.2216537611961 +18.2377939839402,8.31748942406016,46.5004021692778 +18.3214336725465,8.25922806605207,47.5887093976382 +18.4242017221367,8.37405470238301,48.3045179414428 +18.5178801976624,8.34839979186673,47.7582143213791 +18.62536073421,8.55994447224273,49.2508442665362 +18.6651767077089,8.68039099257228,49.1607311301972 +18.7350453514703,8.67663307087175,48.7684500692379 +18.7643724011415,8.65079200463362,48.1386194185733 +18.8199551492893,8.5948517448136,47.2571158512295 +18.8723884721975,8.47099097942429,45.184140188857 +18.9496069466102,8.44494604102313,46.1173048835278 +19.0418088333422,8.47479868053315,46.7808425797958 +19.1565123025098,8.49808882440948,47.4639872146448 +19.2734658291365,8.61065456327486,46.2182105772222 +19.3971591938044,8.7956940186994,49.5771487801314 +18.5850809595457,8.50274189813154,49.2495193909706 +18.7517028720217,9.00521563050649,47.7924664301916 +18.682189173075,9.05617910845527,49.419509952231 +18.065264852585,8.57689995844686,48.453881141914 +17.8962513436759,8.69714788484008,49.7478818186229 +17.8920618051089,8.58709241799807,49.1895725959719 +17.8782231393927,8.37702430973208,49.589682011536 +17.8197528935222,8.64695643809184,49.5353703965622 +17.8364878952739,8.7269248341214,49.087251935216 +17.8462463568099,8.78905682919907,47.620469811389 +17.9089513858973,8.72582959188458,46.0556691940944 +17.9845433825281,8.70780024999802,48.685008137643 +18.054814066489,8.5513644050759,47.2032816075435 +17.8326669088001,8.0971236967931,45.1205573004263 +17.7343282239964,8.21749844646701,45.6291400574343 +17.6398273979435,8.31637072381969,48.3296257369617 +17.5578277014539,8.44629845839345,49.0266498530759 +17.4761594038286,8.58126314120251,49.7750447171481 +17.1916388092128,8.68407845469431,49.6763741911744 +17.388627283362,8.73351318054865,49.6191434151096 +17.4914287816395,8.49042812019404,47.2779942075193 +17.5395260902632,8.36193220694992,47.4197270486123 +17.5730644937175,8.26092332964806,47.3798030152273 +17.5636933333947,8.15190624098662,49.211887135514 +17.4108570618422,8.2595801519886,46.2585307113123 +17.4398037471213,8.24721028789343,45.4390226669538 +17.60237446844,8.68622998524936,45.3016774927895 +17.5927177650552,8.73244332168972,46.6324376481116 +17.5406792952445,8.64960697482727,47.9318295723734 +17.5296948456078,8.6504217456615,49.1538812914683 +17.5273984859968,8.42136794374452,49.8723958013503 +17.798930923658,8.28494199763635,49.4050858041085 +17.8107191739789,8.2947351243244,47.1268083655555 +17.9548364413861,8.38171669821244,45.6236995414703 +17.858546294829,8.35023097720678,45.9850016982899 +18.0242202790923,8.4835146575126,46.0253527717248 +18.9113455198892,9.61522755602547,49.8564430255381 +18.2195718025579,8.4472662190798,-46.8695023292235 +18.3250139893611,8.40630181634576,-45.9076543008258 +18.3833972870153,8.42054245986601,-45.5854858009537 +19.3845783568307,8.87278249190635,49.7588828413151 +19.396917719407,8.83463309337352,49.7523648010875 +19.386973854495,8.86314981984487,49.7219928694055 +19.3867954838836,8.78875190090132,49.5745538975027 +19.3656952865575,8.87221741319193,49.1131545697218 +19.3100947309646,8.80141278079565,49.6682843714126 +19.7945289794567,8.81526477076515,49.9533019830348 +19.6646457408989,8.64833886352959,47.1046751047783 +18.4171112751274,8.29766023121995,-46.488712026328 +18.4871912874934,8.38698077605423,-47.9849677697505 +18.5221102709423,8.3436992718552,-47.2594061219183 +18.593163169088,8.29235845187045,-47.170280283992 +19.6907017858008,8.75028323987713,45.5182432321992 +19.6388820878384,8.8448453557389,47.2317833022856 +19.5096824177241,8.97490303510786,49.415121612347 +18.1261994168533,8.79289846136138,-45.1978531916168 +18.1376055616254,8.94335275484867,-46.475825925064 +18.1382725162596,8.99349120217227,-46.586160565838 +18.1483956876421,8.95180219532823,-46.6034911576947 +18.2444920632189,8.74210934943968,-46.6309490946631 +18.3680689387452,8.54842461636822,-46.5839036967699 +18.2908134129442,8.65290949291823,-47.7260833840518 +18.3411784402548,9.13737881616256,-48.5490986261046 +18.8237506869744,8.59415114311912,-48.6436898949631 +18.3609886486034,8.56294890840192,-46.6618382722515 +18.3069607845134,8.66480066115165,-46.4526867206489 +19.1636715802241,8.63878930589793,-45.5092998183522 +19.1082466432498,8.5781276231151,-45.294152858868 +19.1255748407455,8.49038848517457,-48.6999814018456 +19.1923770846998,8.47608520983844,-48.8734319581792 +19.2748289898819,8.36741851900295,-48.2464091655733 +19.3398817660505,8.38338145739278,-48.1196668234159 +19.3685649482375,8.47147682642469,-49.5905996051969 +19.3458103301993,8.6763679305497,-45.8772505779717 +19.3020054607968,8.72246707218945,-46.5361274274198 +19.2979034594378,8.63349331630296,-47.4332044495752 +19.2516897864106,8.48762953427914,-47.9017924743456 +19.2161955008399,8.31234863884947,-47.7624807446368 +19.2166335951446,8.06506839717874,-47.3977155537714 +19.2310061002119,7.92457588088441,-46.2036992032388 +19.2549488070826,7.85409341112285,-47.0472588247466 +19.2866184392222,7.89347781621514,-48.2776264396954 +19.2661104009802,7.9042485602653,-48.4859247324243 +19.2337932288895,8.02663221702834,-48.5333868148106 +19.1938494082468,8.11337430497611,-47.5131080315469 +19.1758650899425,8.22969407864442,-46.6559887225371 +19.1491919871278,8.3882029604995,-45.3733645256975 +19.1610980143582,8.63438580780063,-45.4634247169939 +19.1668822598586,8.88091353540409,-45.6914981547205 +19.1707972811368,9.1142731721717,-46.7010554863261 +19.1292278689291,9.21925889413143,-47.3586490976197 +19.060582702516,9.17678872554291,-48.8218621190378 +19.0226134190547,8.99852335877551,-49.461900720353 +19.0119435976842,8.75329717375283,-49.4532006578605 +19.0020685279486,8.46171105181677,-49.0939367122466 +18.9730413260426,8.27657863238649,-48.8997098656946 +18.8633110667853,8.34022179309703,-45.6793095786193 +18.7607205254098,8.36394562926824,-45.4673641562505 +18.6832406609458,8.41272866196235,-45.3238328550668 +18.5971547453594,8.49653461114704,-45.7954058967829 +18.5128865448762,8.55303680956007,-46.4052484802337 +18.3987940728489,8.66277042556567,-45.8696062699511 +18.3374032690538,8.72001477494026,-47.3782002215237 +18.27585352953,8.82734813189316,-49.6555092689137 +17.3805208796872,8.93211396720568,-49.8163245280538 +22.4916181450631,8.25673782864283,-45.4852400380696 +22.5429664183219,8.29482717442708,-45.6792698862272 +22.5740247222617,8.35945157438305,-45.9865372769905 +22.4820710439156,8.4396824420333,-45.5232911270494 +22.5507769995115,8.43698649379756,-45.9634759525248 +22.2985171213819,8.6252434307361,-46.824226004858 +22.3987910060003,8.52388704628995,-46.318494298342 +22.175694732239,8.70836702476026,-47.0559734760359 +22.4389124592156,8.45086439562535,-46.165258339268 +17.2372335276539,8.93134819361705,-47.7587987444974 +16.9555135119558,8.65422539487906,-47.6193909095214 +16.800252825365,8.36206887450204,-48.2019601845028 +16.7351683830522,8.31505695347142,-47.4355011316599 +16.6797152730461,8.3310402595708,-46.1869647823116 +16.6467520496051,8.4231782632209,-46.0798936240593 +16.6128739928715,8.59057661177693,-46.1154193825024 +16.5824484254307,8.69226926371005,-47.7077833288295 +21.6572339825376,8.32154302760569,45.4391398377314 +16.5721400696355,8.70921999848297,-49.157537942027 +22.2745909433481,8.32998525359287,46.8316305468538 +22.1956086020686,8.27090265973594,48.0754788402838 +21.9990063992749,8.1701142587914,49.8731949043228 +22.1215326546309,8.2164790305871,49.5518447259108 +21.8916573479798,8.05714883136659,49.535566082947 +21.8698311796052,7.90184628862329,48.1419081054399 +21.859948591602,7.72617305110077,45.8846391542822 +21.5442622207072,8.04400753679659,46.3284489577256 +21.4548362806737,8.30915653620577,47.4390632404629 +21.3507281961471,8.52251496097694,48.9339702332534 +21.0547434751911,8.82107917820619,48.620362784123 +21.2237371282571,8.66034670576629,48.9645886251122 +20.7589214155204,9.10245176276632,49.023381295358 +20.6427968851846,9.13489143784717,48.5354232652885 +20.5355882207842,9.11569629664445,47.4539562252889 +20.4338162602402,8.99735401313306,46.125880740619 +20.9053570022066,8.98716165298374,48.6760062579214 +20.0915443659397,8.63578364319799,45.3381138810069 +20.2199034468588,8.72797967487455,45.2401179327293 +19.9478782287698,8.62321176214117,46.4493207250698 +19.7379748891353,8.79411647265169,49.1874598582275 +19.8237238552191,8.74603829366342,48.1908745255354 +19.4746243890681,9.05917143979689,49.1103057594677 +19.337183303776,9.1004876396432,47.3372953701648 +19.4673789547555,9.09837627410212,48.4225885449472 +19.2866475044125,9.12673885955937,47.7673441425444 +19.4519042437241,9.1019314502149,47.909126337316 +19.1857404853116,9.19020175289002,47.9689275593268 +19.1286926928328,9.27873797380055,48.8189722811767 +19.3985772144003,9.12582211967586,48.5246295578896 +19.162276204761,9.31480047843021,49.314504793443 +20.3090238607567,7.98952149253607,49.3230055787922 +20.5652184746229,8.03933931617364,48.1347967908629 +20.8298750657714,8.66887417982609,49.9880162524821 +20.7283522528495,8.36143146842522,48.9225957834146 +17.7857186720727,7.72674208931194,46.9841430956127 +17.9039416677466,7.65847074094599,46.9846543000675 +21.7738447668914,8.85780564999136,49.1563238103045 +21.757255037422,8.89469275788036,48.7196296748966 +21.7581282493834,9.01922665696638,49.2392124552316 +21.8234152852176,8.78617598373115,49.2311621724225 +21.7457760432892,9.03378513599137,49.1349236106817 +21.7967673237442,9.0198235479269,49.0571578807265 +21.9284914266244,8.95486054004442,47.7015090413632 +21.8749992777543,8.69776762018618,49.2135456210387 +22.0414848326439,8.9135528818842,47.7493072610932 +22.0516684838705,8.84672805397953,48.3260609005397 +21.9628223610144,8.78680575589009,47.1119079824859 +22.0451064800206,8.76204495875363,47.1033721871557 +21.9589722002866,8.78304805616668,46.1847322092891 +21.9287197260363,8.79200341104073,45.2263358809737 +21.9917560653226,8.77395263100977,47.8387037511937 +21.9556951021072,8.85741386165793,45.5466535020297 +22.0250567732997,8.83656179569598,45.4343871345527 +22.0340066948457,8.77323855187405,45.3032033538029 +21.6069212365616,8.85206270933819,46.4129465714341 +21.4890202698289,8.58723799989233,48.5162062042956 +21.5473464992345,8.69894214313079,47.6170293013435 +21.4609383405615,8.46068421079177,48.452823281007 +21.4451355610241,8.35447392578002,48.2678193395608 +21.4307508967091,8.38547624666309,48.1896800162624 +21.4055335683639,8.46518397946764,47.9632113882475 +18.3369958814456,8.40885673638442,46.8164329110695 +18.2876602924923,8.38118398056916,45.975797672795 +18.351431518541,8.38309851168924,46.4950663476566 +18.461220384994,7.94377921681102,48.2516338414312 +18.4645373095887,8.04160595088546,46.4901248171553 +18.7697025912414,7.49000983881103,48.0211882685308 +18.8113909846407,7.57669748873672,46.3694605888335 +21.3794966907656,8.65763244858685,48.4709566873598 +21.3800338843598,8.74976097197282,48.1637777165786 +21.3825144806948,8.89880403602898,49.4339938741977 +21.4036742216605,9.033240970546,49.9937320838328 +20.6940211269614,9.33153317921209,49.8378858053199 +19.0730888707545,8.03722593575472,47.5529571565896 +18.9933089081988,7.84027893368876,46.3254922919998 +18.9216127048367,7.73070305490277,47.2450508825261 +19.1744616557714,8.33340333837564,48.0131181937618 +19.1743079336566,8.25235336670921,47.8582084738554 +19.1459926773789,8.23030882035582,47.3424786614564 +19.1455623098928,8.28751172596135,47.1715851122227 +21.0058665952095,8.35750625425366,-47.6042545403728 +21.0471500627479,8.43424121639123,-49.3183089467225 +19.7295952276052,8.0224636700265,-49.9348864082052 +19.7943842620128,8.0639304290264,-48.2691828892325 +19.7943420753621,8.01906048907209,-49.399068867162 +19.8671695143671,8.14178738840552,-48.1942955169942 +19.8493726260618,8.22511794502984,-49.4841588231306 +19.23521025056,7.97334710904786,-49.7288937346428 +18.8564311976121,8.39955690184825,-48.4308639699528 +19.0935119726808,8.05894535622736,-49.1680789765449 +18.9522523067119,8.19724313051201,-49.197767800184 +18.8469229559968,8.59842579943313,-48.9077973702812 +18.7906873076957,7.91881519539773,49.244564206856 +18.0284649854398,7.94398227034289,49.3896613215427 +17.9967973664279,7.87482220344792,48.0096750181158 +17.985381677727,7.88632866912601,48.3654456519256 +18.6922036775,7.89751796840359,49.5076923107442 +18.8782045163681,8.80006545273209,-49.8778320807664 +18.8444441376562,8.71902083356821,-49.5665086718069 +18.7633621004849,8.05249242838406,-49.1103804184833 +18.8183590644018,7.92430934708072,-48.8220514943621 +18.7596534272219,7.83050067244381,-48.3166400805194 +18.0739144929838,7.96124426822435,49.9254801633247 +18.1194135086757,7.91247456679824,49.2528654602764 +18.5110425341947,7.56648712828187,48.6210566772012 +19.1303157340792,8.41730178814164,48.3732722485885 +19.2489104261749,8.52864907938326,49.7587946726325 +19.3661644289122,8.48671045678412,48.7620684248648 +18.1819026406977,7.77081525113553,49.0248398018477 +18.2706693284156,7.67325451423196,47.0467498141365 +18.5365457083409,8.13677077293838,-48.7319904551928 +18.4892030587672,8.19476486570762,-49.57311264316 +18.7032433981682,7.91787829641449,-48.240102699839 +18.3964331498176,8.13986775224651,-48.6671854999712 +18.6051662110624,8.03963340369468,-48.2734130169089 +18.172597798562,7.94971698235061,-49.2513137760481 +18.2258602593272,8.04148921257508,-49.7916582572809 +18.2865912272823,8.08877288539882,-49.2033819205446 +18.1124333883668,7.91898849738509,-49.3120979372234 +18.0347749720854,7.98161555095868,-49.5142403633972 +17.9718840404465,8.07714691312776,-49.6842779751633 +18.6469680399079,7.93058243251751,-48.1015754003623 +19.5567020504726,8.38062141574822,48.2966671731121 +19.7425103546138,8.2457591241663,49.8773161976107 +19.7823933817018,8.16033362696846,45.8784037307287 +19.7812193503131,8.07263030492186,48.428445488452 +19.7961440145555,8.08240189085671,48.6802929438263 +19.7135181946868,8.04547695921105,48.8905466296112 +19.3852520285341,7.87617202991122,48.8956431435333 +17.6873339406802,7.89542000558167,-48.293154505218 +17.7557911709503,7.61710517530013,-49.4209424812214 +17.7417447216243,8.475166154245,-48.0744513571211 +17.7242373933103,8.4378986143218,-48.8380540226795 +17.6842077334085,8.23418802855093,-46.5186494277345 +19.262176719757,7.78054219725786,49.5105379070334 +18.8738562411631,7.79590455923123,46.1984856071135 +18.8043648047913,7.82806920403993,47.9802633614087 +18.7093628919967,7.72027627219977,47.5753965613304 +18.6474910140625,7.76525511743124,48.6366488555116 +18.5654904760872,7.73152460721053,48.7222525052621 +18.4855304423975,7.77844615415886,48.6980965563928 +17.8032688329799,7.93574539379324,-45.244977989756 +17.856605824433,7.88494381136404,-45.3332228852001 +17.8787531151383,7.91482960339017,-45.6500511425601 +17.9365269291122,8.08466727926108,-46.1055780166878 +17.9134317923469,7.95805656535336,-45.8559719342095 +17.995198904587,8.2137648785901,-46.275476923862 +17.9483964051235,8.07240041152446,-48.394392546592 +17.8823607768394,8.07321612606139,-48.6497934160904 +17.8068270176299,8.09613261390276,-48.5076695798686 +17.775580432509,8.03352071255833,-45.1056215330496 +17.7061046491815,8.1503762005878,-47.4282626359584 +17.6872014681204,8.09539159315939,-47.8156881687244 +17.1285069643917,8.12940397530586,49.5675913368769 +17.0783047726996,8.0413584775146,48.8732377071743 +17.0604656127537,7.96631366432196,47.62129133707 +17.0247932109333,7.89948833066083,46.1253909464097 +17.7587317914478,8.13583216115426,-48.0799167597088 +17.8094802469785,8.08780662004946,-47.5022011295586 +17.8429329712064,7.99122661495197,-47.706034330125 +17.8759491094457,7.87481912565264,-46.898129951393 +17.930521083303,7.72963202576813,-46.4535896802576 +18.0615784784227,7.35355659011197,-49.7831074321853 +17.9827190920552,7.5693286164661,-47.7942333191142 +16.9284593425765,7.97846784444729,45.4855072380807 +16.9478661743972,7.99715973924411,45.1739307064338 +16.9268098067333,7.98678690020711,46.0146206429301 +16.9890690162502,7.88054929834979,45.8656416578947 +17.1261200524766,7.81482982726716,45.5737487224405 +17.2734177922347,7.72949251742036,45.6212857519135 +17.4251481999758,7.72680763569001,45.3120989030849 +17.605073151682,7.80446761918255,45.180968837609 +17.641546745478,7.92829783467106,45.2714109571679 +17.6466576155538,8.02196456929414,45.2735767729174 +17.6590855227009,8.06935562401138,45.256135892128 +17.7033707033724,8.14150121013666,46.0767674398605 +17.0074836130633,7.96606736443113,46.5026352906764 +18.472809636322,7.87386476790506,-47.544782063235 +18.4096593764627,7.76614797793602,-47.5470195726965 +18.361545533436,7.8219827921953,-47.130105493095 +18.3095196949432,7.88073667594305,-46.2533155430554 +18.1386122459838,7.9905364084737,-45.1352447297032 +18.0486640759399,7.97584233297392,-49.1141819772864 +18.2364194977598,7.93823199884205,-46.6759038344337 +17.722077415306,8.14323189327656,46.7498190015646 +17.9106108904242,8.0804634372246,47.6860843232434 +17.848380152442,8.11784911127457,47.3338806373526 +18.0236998588856,7.9898276590854,48.0533218343379 +18.0782426532485,7.92057560632266,46.8649850433171 +18.0462914973376,7.88863441772141,46.3603484698004 +17.7401254115533,8.1313146656149,46.6935821858916 +17.9582369296077,8.07776103255283,48.1110096373015 +17.8798892676246,7.82037353715065,45.4201077165935 +17.7898286529928,7.70934623955591,45.9457127112183 +17.7836001418421,7.66491812061494,46.2517154050296 +17.7931701706773,7.85738878920066,47.280505664631 +17.9478058883555,7.85389539757588,45.1625416327718 +17.7922331364668,7.8185309788304,-46.1834355759953 +17.9228402792259,7.88362301023158,-47.1592961286679 +17.6873085694965,7.74351985143677,-45.4424066627595 +17.6093884705534,7.84797830424316,-45.8799652496835 +17.5252599654088,7.82732022709985,-46.5018253233215 +17.4550974020325,7.8593346310378,-46.4972895737056 +17.4364692967609,7.83339231281298,-47.2494616337942 +17.43818783135,7.82977901988804,-46.9330906242572 +17.7726662999957,7.95299394270836,46.8102407062337 +18.2731822077351,7.95168897037523,-47.743405326537 +18.2778357883905,8.00070237852426,-47.9693552609186 +18.2707783783133,7.89587809345951,-47.4927812372293 +18.0578879808089,7.61412456582291,-45.03201319537 +18.230634179818,7.79127294420979,-46.6771341077786 +17.6058934892831,7.48113845900855,-49.0590605436255 +17.5033206809715,7.40557775594165,-48.9511182521917 +17.661829184439,7.53850999208201,-46.8111885077865 +16.7930760787589,7.44821429902409,-48.7186483844102 +16.6393975710434,7.79469908068053,-49.1097561502999 +17.3933886112711,7.34724942810932,-49.6396594920527 +17.1409209238175,7.27067209902595,-48.8958021769532 +17.9156950221966,7.82233704375975,-47.0636700397977 +19.5706697378195,7.33762118289379,-47.30583522612 +19.6590939990306,7.63233897557557,-48.2016009285023 +19.6779961164083,7.77261495838281,-48.283871907602 +19.7047764292897,7.90413525197414,-49.5590206983681 +19.6433617922482,7.48926919845156,-48.4001194265308 +19.6268690317066,8.13198996985562,-48.5827361120943 +19.6633046592829,8.01251180541323,-49.7433997672775 +19.2698521645474,8.50301311797118,-49.2001777015485 +19.0708847433179,8.50661746944705,-48.7678010047466 +18.5550648129223,8.35785966909296,-48.131014403746 +18.8926431379457,8.45778906778299,-49.017375957857 +18.3998003305416,8.31678282257615,-48.1707189638871 +18.7219413529536,8.4096764604496,-49.0398535028394 +18.327341214034,8.40737163363056,-48.4093295024987 +18.2904086974306,8.42078820785487,-48.0875132583278 +18.1123086849879,8.50813541301016,-49.837426676632 +17.7448413486183,8.37768134774598,-49.9738309147069 +18.5494656891797,8.578880642969,-49.6761590961492 +18.0788970015206,7.05812592843502,-49.3595993520749 +18.1654668410579,7.48009591435271,-49.3750325167496 +18.7862951364589,6.79856270120508,48.1322492633287 +18.8086046984525,6.43776019971847,48.7569010776664 +18.7227979827974,7.08946422094158,48.242457865619 +18.7014616156191,7.1780285488549,47.9003039974801 +18.7517972826312,7.23818076752387,48.6833912771068 +18.7532963186913,7.28506624828137,49.2877408125817 +18.8104147084959,7.47199315933164,49.8381540602459 +18.7717915351889,7.34522264338204,49.7891106088355 +19.2989014757869,7.66691637651791,48.5180411061118 +19.2405326035921,7.76454722474063,49.0214950976893 +19.2368105484893,7.90255731722751,48.5277030873202 +19.2034103067703,7.92763069435175,48.0462046529778 +19.2077811398815,7.96809380551318,48.538330341354 +19.2273546778232,8.02098583624006,47.088544514084 +19.2461728420033,7.99411039258132,47.6032078281858 +19.212465086545,8.01724198275263,49.0499295078416 +19.1662239465001,8.0913200673223,49.9624503472224 +19.2156046742555,8.04181384982382,47.5151768171071 +18.5698429415338,7.67410010586954,49.830330398656 +18.4796415290003,7.56170202958925,46.6723202973217 +18.4596440138743,7.48471459491023,46.5479288997828 +18.4713179899149,7.40735540985738,45.020700358176 +18.4811551138309,7.51307864752927,45.863597969581 +18.4833586065492,7.45718152970536,45.685479601562 +18.4588450919346,7.5786947047172,46.3269121525074 +18.419893075804,7.71972443060896,48.3322856920983 +18.3473288155705,7.77399378319242,47.6600285295575 +18.2544704794882,7.90211201116304,47.2268539322698 +18.150386422725,7.99086152721616,46.4488258623478 +18.0150198692068,7.94301243777398,45.7370540653965 +17.9478818959873,7.78814894615697,45.2177350533032 +17.8200444455684,7.22623696020628,45.5128895930276 +17.7032733232561,7.46867256318023,47.8964158999332 +17.6621444872094,7.6524163815303,49.534447750032 +17.5681324099508,7.80649032197906,49.9508865480685 +17.3280929767907,7.80791738892002,49.0802444563127 +17.2930306362233,7.71713621506122,47.4305452549595 +17.2702744370606,7.69490430871288,46.3388023627817 +17.290926399201,7.68397261247383,45.0117747004973 +16.3531050001166,7.93119662788625,47.6558992867039 +16.8621293619362,8.12682807750769,46.8882803534161 +16.6715941668258,7.82879329512043,45.9748791229741 +16.7882094182087,7.92739575117643,45.0525447390417 +16.5849464644365,7.71564325202344,45.7375208748912 +16.3819419435999,8.12942716076613,45.1082804707821 +16.4437321287654,8.11835446958095,45.1029670950904 +16.5304406965367,7.97100094967131,45.2774520008449 +16.0987447253793,8.03606251775906,45.5665590150584 +16.0290400544373,8.12837735528397,45.4487292933388 +15.9491130262325,8.10445581863596,45.2417162057584 +15.9550196249527,8.05121337140752,45.7066053381361 +15.9727088337757,7.93082291197385,45.0782211188132 +15.9810039262472,7.7213749200082,45.2856184499087 +16.046658906138,7.75297435578434,46.9628042244587 +16.0422558157104,7.72255723994002,48.979259688993 +16.5751912404226,8.01623147092755,46.376861867484 +16.5758123879862,8.06277604188702,45.0452002958313 +16.6457054741515,7.47194283051341,49.5694227702314 +16.0377953401792,7.69554055637471,49.9280637989333 +16.8797025078347,7.55966609300302,49.2871476821943 +16.4775756763222,7.4037439266234,49.5658616630672 +17.024711340985,7.70705100753738,48.7167257880047 +17.1285459418831,7.86142915246699,46.8871113950843 +16.9557227605268,7.91353198617822,45.4788244401497 +17.0298094276957,7.93829483751715,46.3582622689414 +17.1540334138371,7.94877196899647,45.1473919156684 +17.3395411583476,7.87816922542234,45.5825437307665 +17.5120763259907,7.85161063750904,47.3000553891651 +17.6369776544301,7.7953410660992,49.0511554537101 +17.7862585428313,7.76547110928819,49.2952590956988 +17.9535240076196,7.80342705430902,48.5365601504741 +18.1147174803639,7.75517884702632,47.9031520546253 +18.2710546477948,7.92098788360132,49.5677988141567 +18.2429373129489,6.99594909172034,45.6613616805348 +18.2381523784855,7.28590135504333,45.8537037169511 +18.2878913663843,7.06360357878938,45.7911263870681 +18.239777842755,7.61838200070492,45.0686669663892 +18.2406108778378,7.93087462878446,45.1997282586807 +18.1555943190811,8.26444292627115,45.1148587268798 +18.2514020433955,7.05300739421579,45.8268471436498 +19.06310427329,8.25440431299257,48.261588155398 +19.1044780136524,8.27239934884595,46.3519998270933 +19.1351643466885,8.2454257059079,45.8687590259856 +19.1684233517893,8.13800569580207,45.3587154204027 +19.1687576973604,8.05077667397426,47.5731791107661 +18.7756174494318,8.22534734345535,49.6728142827853 +17.9451305222006,9.24357028445364,45.3090149881596 +17.1995044324551,8.03477645359325,48.4312705680186 +17.091789708801,8.10905396994786,47.1913092580511 +17.2959961375452,7.72505995059692,49.8105949909398 +17.2872546942258,7.8687194888598,49.1703118624387 +17.8140994487534,9.08890779056021,48.4442818196199 +17.8149697149802,8.81221177872,47.5661543334287 +17.7453262276145,8.54141043529426,49.181528120035 +17.8072536489304,9.29959648454006,49.3098643216898 +18.4603115863241,8.03141539264701,49.2304535272377 +18.4413305247954,7.97765779032514,47.8108937512629 +18.4681424881419,8.05499609599888,48.5606027489799 +18.4702012254652,8.00962879733682,47.9207918210522 +16.9838387332889,8.81054906378925,49.9128421457978 +16.8307514929672,9.29358853832713,49.3806793518084 +16.7291520917162,9.44672423253801,49.3661712297938 +16.660440444474,9.47282146057989,49.1572614840044 +16.6642157802691,9.30117098527741,47.1413520411259 +16.6624215119128,9.40579862572744,48.1946774535342 +16.7060773151618,9.05494372342879,45.7297004862585 +17.0954269317872,8.4637620367035,45.0862110843463 +18.5243989339971,8.486716421551,49.8965002642434 +18.6015196853554,8.49898863536328,49.4215249812232 +18.6795749021096,8.55551994413919,48.4040697532859 +18.7265334194004,8.57526024194618,48.359061430132 +18.7508653129106,8.64812577614083,48.1913430401664 +18.8016827081114,8.66892475246815,48.9365369123838 +18.8392986850963,8.64305496724586,49.2040456384769 +18.8717725485781,8.63842789753717,49.1251336979642 +18.9010625574024,8.71395454474504,49.8163407342827 +18.9159720337343,8.64593907080731,49.7109085169001 +18.9072061303911,8.5738064339871,49.3437289613516 +18.8295323929216,8.49751064020797,48.6037607539968 +18.763776771857,8.35017947622621,47.1514835298437 +17.6684819665068,9.04190952468885,45.1329725968006 +17.7935512303134,9.11695106659465,46.3283704769742 +17.8994874239703,9.0799072086492,49.2479216766557 +17.8823882395465,8.79675052021123,49.7189601797504 +17.8099349258714,8.71197032926472,49.5958086141058 +17.7131501919199,8.77378689284885,48.8223325095345 +17.569392499593,8.80023569792166,48.3559240966034 +17.5343128684853,8.83997090445073,47.8941079113377 +17.6347890949149,8.78989386132414,48.5860720583268 +17.4150669466005,8.93343976868973,49.1364509573365 +17.2230055476264,8.91049344470908,48.5544805717449 +17.484358285821,8.91261939080103,48.6683468964114 +17.326643060831,8.89672890937414,49.2466951890695 +17.1054432139436,8.8860849933475,47.2690842333812 +17.0128045293159,8.90577755171648,46.9100344014695 +16.8863485600495,8.83974268733878,46.3577382857513 +16.6344078425555,8.82434658876615,45.4993439557538 +16.4877832139068,8.84622485393287,46.0084859664507 +16.3517490713755,8.87607721751771,47.1367924855051 +18.8095204716901,8.04430583630535,45.7018696302931 +18.8310227725743,7.96906340118455,45.196754097278 +18.8890075171631,8.12045975023629,45.3491022589029 +18.9160350518124,8.26105440491918,46.9352355715575 +18.9282396760743,8.24851491622035,48.3472617644575 +18.9125731626531,8.20664116021426,49.6984786724354 +18.8351963604901,7.82563094899089,49.8336268122732 +18.8317715571688,7.57798000207643,48.5223835096928 +18.8217709183472,7.5850330309647,47.5650812473964 +18.8810092713535,7.72717578527105,46.976989840072 +18.9865953173182,7.83855448757515,45.3799471618137 +18.7595217711792,8.21076437570512,47.2448281570468 +18.8571603175474,8.04019256440919,45.5527354858465 +16.2799229816475,8.97489828016406,48.7630801996408 +16.2017711371059,8.8533648580779,48.5688676334865 +16.2222209387431,8.91963460140314,47.7874895369386 +16.3390244396389,8.40136407037759,49.8882687311533 +19.0566479880429,7.98805237812869,46.3284988574474 +19.0344135064715,8.00380256316875,45.2804744266772 +19.0555287855574,7.97637739541975,46.0303761048476 +16.5069350235526,8.60168008507306,48.3066739607602 +16.4949086056511,8.54369115360153,48.3925950446247 +16.4997505989121,8.55399477351476,49.5406148856815 +16.3393404396945,9.80147849062079,49.8206927159664 +16.3729460541676,10.0151956813217,48.6199778260682 +16.4839170398769,10.0869051979829,48.0130026064315 +16.6184538872961,10.0780568196763,48.6710206907157 +18.8847269404566,7.9672963615562,45.5563500628958 +18.9159020946873,7.8859955242252,47.6697165029177 +18.9197330763548,7.86409957795676,49.3440547542979 +16.9190041821186,9.59323357768247,49.2615948033178 +16.7543549124686,9.90132887128748,49.1152555829305 +17.0446173368229,9.34544505598379,49.8399407047173 +19.0258686622906,8.02373720005801,49.0813237275688 +19.0125494056865,8.04916303385738,49.6878310797726 +19.0139136224931,7.95990273173468,48.1159325938531 +18.9817883077034,7.95997969687529,46.5083850325781 +18.9349696577015,7.99856550365283,45.1275518504242 +18.7236160711941,8.10964665956891,45.3289529637821 +18.6334003809827,8.36826656467506,46.4528976631632 +18.5804003070168,8.49054159284974,45.778374679137 +18.4140152884246,8.62435186067325,45.5551947280756 +18.4868318463635,8.6000849299632,45.9965799886416 +18.6287170323705,7.62211252077902,48.0694663405189 +18.7151617909621,7.53729219806721,48.8354441009059 +18.5552930386839,7.84087797258962,46.9137535983832 +18.813403230658,7.2810339130594,-45.7174308566914 +18.8241192053276,7.18818265113865,-46.0646999090753 +18.8066770837176,7.16403428548036,-46.3112472281611 +18.8274614458992,7.12140679440116,-45.8059060141908 +18.8533075721049,7.26168979340797,47.3609867034613 +18.915742829721,7.35448572012672,47.3333636359471 +18.9424908208958,7.35464696755365,45.7347518774465 +18.9704495675323,7.38971395504286,45.8385754204873 +18.9885206500653,7.50092070267401,45.3067643718766 +18.9373807328867,8.40383075058961,45.935111875406 +18.9063602997712,8.10383351634698,45.1167689013588 +18.9314871802421,7.92789806298193,46.1096265909659 +18.7540395206034,7.32648760217898,48.6777714703548 +18.9086063342575,7.30834526091774,45.8009238880481 +18.9812569656815,7.77844255321524,45.4268230301731 +19.0915878138436,7.92979977486706,47.3033407233107 +19.1211833529385,7.94876861624359,48.1264798085797 +19.2029828145539,7.89379355356865,47.2589024676605 +19.3247663622382,7.8641409381287,47.0949205145603 +19.4309248434964,7.8902389949,47.8273070672271 +19.5534797655073,7.83808611176478,48.5423653417343 +19.0485566946999,7.72960374252289,47.2807802794529 +17.4268663969816,8.56796416560331,-45.0521454348848 +17.7092724694456,8.45876022254261,-46.9055174374403 +17.6491560274451,8.54512267205192,-46.2754283883891 +17.5380980857059,8.57899854325449,-45.4671887507638 +17.7854092208121,8.45212535392127,-47.5653145107138 +17.8402082279244,8.46643437639547,-48.2924908900179 +17.8747679395599,8.48242630036598,-49.1291634633851 +20.4489390121879,7.83789100955184,46.0463573195899 +20.4788881731803,7.88331157430826,45.3888232146763 +20.5189728801417,7.63580271726077,48.6332049365823 +20.5776115727668,7.53305534743039,47.8422065731099 +20.6357164767333,7.50468082408622,46.7739942656013 +20.6810159607578,7.47250023471915,46.0967138587552 +20.7095123835007,7.63527456029251,48.015491356637 +20.6876241027191,7.63351953088766,49.2815962526451 +17.9796313790447,8.47460378896577,-49.8197789870142 +17.8881308801916,8.43838124233917,-49.2788082945108 +18.0701253499569,8.42882028385712,-49.579142582482 +17.8922265906775,8.51428723618229,-49.5090235995365 +18.1148444346074,8.38523414211859,-48.9939124688679 +18.18677990692,8.31714330927566,-48.0577853025564 +18.2403760737978,8.20699119083054,-47.0146146033223 +18.2849136939918,8.17959189397226,-45.2732063073429 +18.3688604785057,8.25205350385326,-45.2139616597456 +18.3760541646075,8.50783676844951,-47.6423071903597 +18.3838862381799,8.37330516964611,-46.9857609043731 +18.3791679905983,8.57384485499681,-47.7580296699738 +19.4653885430966,7.86001559669188,47.2049854913047 +19.5928393718625,7.65845257282923,48.2989752947551 +19.348851045308,7.95537268221807,46.9754714902751 +18.3909258714243,8.36116150853586,-45.3870503215049 +18.3957131906598,8.51601978054966,-47.1634500867055 +17.6710721172667,7.99167930527591,-45.2510814114211 +19.3118933564968,8.08343009477449,46.2964919612798 +18.6805101246523,7.91305561408655,45.9076308488183 +18.5348054125097,7.68529928749345,47.3205829371178 +18.4607421143528,7.42074608607696,48.9455772733939 +18.3827064736562,7.18291635175665,49.6257014344059 +18.4298458609228,7.31782104932089,49.7387063152906 +18.3039503856425,7.26773193957308,47.7160192985334 +18.2361588655593,7.30371246600078,46.5247968360638 +18.1842326249063,7.3402502444872,47.3052245305925 +18.3257540547926,7.26880154114041,48.7455906428226 +16.7915919414461,7.74295419147415,45.3214143115148 +16.7521120692538,7.57527620558522,45.8396817735863 +16.5240420819465,7.55724869296403,45.2633040365055 +16.6383183475353,7.44990971995902,45.3356415617571 +16.7287363747025,7.40248257997442,45.62683150547 +16.4406051765643,7.82248395426587,45.3643813821992 +16.3143057633454,8.04896069773487,46.6201853349776 +18.0634657506824,7.33653605817588,49.9653260304116 +18.1485075978351,7.35812867739757,47.2238836990195 +18.1126521312641,7.39508148252337,48.7207861031221 +16.1960257866689,8.2334696026018,47.5365318946702 +16.1078364464326,8.33520810094284,48.0655004039355 +16.0266757134595,8.38538204593683,48.5603915412937 +15.9796786425216,8.39735736966315,47.9901253902265 +15.7629271846111,8.29448546097326,47.4674774914302 +15.835543514939,8.31330561511387,47.7184516214633 +15.913339172867,8.35498662225485,47.9689203671853 +15.6804749760259,8.34635892923101,47.0523365804981 +15.5161901483715,8.65563413659036,47.2858033661398 +15.6231370616245,8.41795811570109,46.6468031025627 +15.5721814360558,8.55230909207982,46.6064062867033 +15.4953161531717,8.72471719356483,47.0606109251921 +15.548177394711,8.89548315288244,47.3161552104652 +15.5867263383189,9.01159478176015,48.5429798561668 +15.5208460648686,8.84618937119377,48.1003939045212 +15.7423486749692,9.22484994966987,48.0151546676144 +15.6484771442117,9.05963102403163,48.1568583192968 +15.8590417440009,9.11697349010911,47.9841396354165 +15.6706112613175,9.13864096728637,48.333276966252 +15.8833161673136,8.95762994823192,48.3715831115375 +15.8137342971656,9.18606230081308,46.9715464038439 +15.9242922396704,8.69753652780497,47.6087749067084 +16.0711381461845,8.47267724914594,45.4588897389526 +16.189618254418,8.49380362025541,45.473949374698 +16.4934727200188,8.70693419070591,46.2468295239594 +16.6160052627391,8.88368237518873,47.6043465350789 +16.6779465862168,8.90956728949303,49.0030856117287 +16.3387339159899,8.56809409938254,47.3738244258452 +15.9729825236446,8.5783387652674,45.6206804704115 +16.657706224506,8.92728711884423,48.1497200293033 +16.6663712733638,8.74112203504443,48.5813598040091 +16.7171179502981,8.65603442555756,46.7587578649106 +16.7392964907396,8.6333428762235,46.3279704582364 +16.6574249174185,8.83036971264592,49.0387819427153 +16.9385249022377,8.69375568274978,46.80983635752 +16.6935007550588,8.70940130964116,48.11293956031 +16.8285124456574,8.69780397779898,46.7407688656152 +17.1544356470843,8.68728615355326,49.4512339785556 +17.0402586981094,8.72853596186125,48.0549632756537 +17.5038576513966,8.24410156862774,49.1448859647231 +17.4520195796818,8.33620803061221,49.2665731212664 +17.3964535613105,8.24076278877933,49.3419007869189 +17.3451780475099,8.14262088351957,47.7401876581527 +17.2832194506303,7.98412456137755,46.3009647160912 +17.4250897774652,8.35023800114306,49.8478758309228 +17.7321412013318,7.75014843765254,48.4461577172087 +17.8099127747883,7.76634730231063,48.3255667750771 +17.6907777113776,7.80138470863081,49.8447634529318 +18.4300673378992,7.73967610200021,49.5642854245046 +17.2661649956889,8.09492845289782,45.5097440796505 +17.3429573391758,8.15383090462632,47.6549861404098 +17.4481849959667,8.12718416987319,47.3397302451191 +17.5095084064236,8.22562908130804,47.8563601137428 +17.5353190445631,8.3050431658163,48.0854374837733 +17.5959375803188,8.41535642376734,47.8004633586372 +17.6303946214949,8.48697161722222,47.1883118883977 +17.6647047286773,8.44418128839959,45.7289628215352 +17.8694585753106,8.42356847031084,45.3069136032917 +17.7458291189926,8.46931058554478,45.8731799588701 +18.4796593239645,7.75737232775097,48.5847917929669 +18.5634877510514,7.82625783514163,47.4195858637279 +18.5163696771271,7.89319347138544,47.0464566968779 +18.4619520757373,7.98513187718065,46.8146301992961 +18.5653317194316,7.75106439074203,47.8682649526079 +18.294598875245,8.33465650391506,48.8001432807796 +18.3877096734177,8.18897512400567,47.8945383910218 +18.0409657435443,8.4009468445848,48.0684737113301 +18.1752606945936,8.32617364858884,48.6128049801767 +17.9400630199077,8.38652980030371,47.0211431187132 +17.6421645983634,8.56827518264668,46.2192390838453 +17.8297709130124,8.45455917451876,46.3001775364207 +17.7320963526363,8.54097945959864,46.1310524914317 +17.6061092124279,8.58199686780671,45.3611984493303 +17.6319709477859,8.5933746801509,45.925228300761 +18.6229974292881,7.81923329090582,45.2753494848281 +18.7435875401102,7.7052195941557,45.0452170260261 +18.8417508910624,7.56591842416955,45.2401752840217 +18.8691830383787,7.45804033178679,45.1925353265459 +18.8383436759474,7.37821050032397,45.1902830798727 +16.9033286553066,9.59593795067855,-49.9514883077027 +17.0010142509677,9.55417216931483,-49.2100379760466 +17.1260037136065,9.5399351346972,-49.5832369646504 +17.3172705030145,9.64949432211858,-49.1329571284312 +17.4448028916917,9.73835134119654,-49.1343112760288 +17.5257419388149,9.94410050803229,-49.0415467984255 +17.6603919913836,10.1662305846643,-48.8458342655678 +17.7390923908657,10.3102224950438,-48.7127886787871 +17.829982986947,10.4480653297457,-47.9254080101902 +17.9151410524701,10.5241695958831,-47.7479702379961 +17.9046530174075,10.6273321794856,-47.7770988986109 +19.1801560813326,7.25424879629056,45.2695534214685 +19.2993027465872,7.23748121975418,46.5136216614535 +17.9044765958853,10.5916752789456,-48.3102281857577 +17.8521137337197,10.6741005012057,-48.3374428864773 +17.7377902702892,10.3378902396627,-49.0146682885078 +17.7705694277677,10.5209264961495,-48.8777729621328 +17.7292486453373,9.95270887152621,-46.8431665365677 +17.7281230196399,9.91316467461732,-46.9239825193167 +17.670903737101,10.1235787092815,-47.6426666484043 +17.7959559247837,10.640201577591,-48.9578394201668 +17.6708161209292,10.2989694633142,-48.359585020776 +17.7239051132284,10.1314084392605,-47.9611019320979 +17.7220269598169,10.6008739960167,-49.123241301498 +19.3993319961447,6.91677367712972,46.3312974289958 +19.2131985029057,8.0478915376192,49.699962592106 +18.2976190076619,4.34117198857866,-49.324406457787 +18.0652586670643,5.93448592937496,-48.8825884157422 +18.0690203104426,6.09739648436169,-49.0024312726121 +18.1165533620887,6.46056494682478,-49.1259612884119 +17.8960192021392,7.96543846816544,-48.9492155647518 +17.9729356635434,7.79615927110016,-49.5368204889367 +17.9198230613678,7.82030891407395,-49.4652986650396 +18.0322776697559,7.79364344838402,-47.2211949133746 +18.1257014562047,7.81254756785736,-46.1551626440288 +18.1635977985512,7.82359503263756,-47.9494013112459 +18.2289262147585,7.73435275537996,-46.9211836950487 +18.313205552464,7.70213482772403,-47.8126476260652 +18.4058518614517,7.71683607489561,-48.2045401827198 +18.477318936149,7.79688600355062,-48.0035976463536 +18.5157561904409,7.95994316305205,-49.2029770356547 +18.4887604648445,8.14570792127048,-49.0412715306913 +18.5105318084398,8.36609092954561,-48.1888443045098 +18.5459244134303,8.54408815783207,-48.61125562548 +18.8620366583739,7.52671795450231,-46.3057268348772 +18.8984734525562,7.38459073587438,-47.5240512207282 +18.4975988074429,7.94761035875656,-49.47267435947 +18.4495553611952,7.99874388833428,-49.5264486285553 +18.4198244602103,7.91260108700267,-49.8555266109283 +18.3894760650122,7.91648214640713,-49.9627230865599 +18.5215625372552,7.99937813843953,-49.7535607400222 +18.305116705519,7.91190399803886,-49.9496979985601 +18.1251596392867,8.68715149613055,-48.986438292179 +18.1250433677831,8.61296008576518,-49.0373552357808 +18.1065615216137,8.47007888504882,-49.4357309251518 +18.1412927143492,8.51224055251483,-49.8353055241119 +18.1141737628146,8.52466554132177,-48.0153745559687 +18.0006159764561,8.60554238214944,-46.0053628753402 +17.7866603671741,9.18479206711742,-48.5058648065906 +17.9016545789229,8.94364927756698,-46.3514247288567 +17.9507406665986,8.75906105872256,-45.5118210416258 +18.6174717991192,7.40629549655781,46.7108220681971 +18.8705237306564,7.38042371816016,48.2987178735654 +19.429725532778,7.72655437134984,49.3424034273502 +19.5160652645676,8.04402075289389,49.1217850470924 +19.6136120429605,8.19735412180904,48.3789219769168 +19.6858298889932,8.32040975174841,48.915962477712 +19.7818380586522,8.32909167440383,47.6324527190121 +19.9078891323081,8.2611789815105,47.961229405667 +19.9247007412387,8.07676391316338,45.9417219355587 +19.9326155803147,7.98544230533101,45.3980602141842 +19.9537478291505,7.88876277078846,46.0508217857717 +19.9419162608851,7.86598278550965,46.4469369621238 +19.9542565340411,7.79869415106063,46.5886401941388 +19.0717066813932,7.36881286794472,48.8132725762466 +19.8690085199978,7.90146904929908,47.4685637765338 +19.9259204653203,7.86418775301711,47.2197569250134 +17.2614194482497,7.1148223058313,-48.8347926567492 +17.3566960976888,7.07235683427302,-49.1334782225099 +17.0602350255969,7.4436020053772,-47.9310333340867 +17.141216924078,7.30144197140083,-48.7880485032815 +16.9601292736282,7.80879699976325,-48.3525156687697 +17.0287922906859,7.63513899550056,-48.3797719529741 +16.8806638326336,7.85623051966347,-46.8306812057741 +17.0428704134823,7.78787034423739,-45.4252637541497 +17.6600901367921,7.45333884168561,-45.1319723052079 +17.9195244023693,7.90137297121087,-45.5310986244903 +18.0216085587148,7.85140589695806,-46.1965868450425 +18.0568563983832,7.45447778718751,-45.0295433725071 +18.1395604559202,7.25940302673618,-46.064505012861 +18.2885980964182,7.01916360465491,-45.5235440413818 +18.4007258264159,6.9403049723386,-46.9660237571788 +17.9779976635566,7.90585450306735,-46.3749813746738 +18.036072830909,7.6270352203448,-45.7676822439282 +18.440209332943,6.89286278483594,-48.7663542874959 +9.83019383253382,5.53771526961454,-47.0069848900719 +9.79930719248391,5.54065472561463,-46.8185424373723 +9.68395491418524,5.6063071447144,-48.2457493321639 +9.6424684587909,5.60304213519508,-49.3620331719105 +9.71158042783583,5.64054582339201,-46.0487362560892 +9.70853599834536,5.65320620771891,-46.7726019524058 +8.3488424354918,5.66578434907172,-47.3183684323831 +8.38074310801547,5.62333318018339,-48.6906148348289 +8.54844786658728,5.36598162126005,-45.2581662745588 +8.59330000554199,5.26941654153184,-45.1382193878216 +8.61457312518467,5.25128141559492,-45.2140872833154 +8.69050506843189,5.62503788983567,-45.8655299517104 +8.67939465471084,5.58865058794956,-47.9534617986932 +7.65157592342335,5.32808756958846,-49.9823321374483 +7.5596922127718,5.25076852924822,-47.5269072624722 +7.62128939957241,5.28250252593249,-48.3225141342566 +7.53491624790614,5.26207094364365,-47.9858046443017 +7.61264047582191,5.24887089936122,-46.4378616297002 +7.44988434104492,5.31985992565054,-49.6198195604821 +7.19576119131829,5.40023773188601,-48.4027476278737 +7.20349519530676,5.4334450985807,-49.6362389640328 +7.1985106918336,5.27430283573835,-47.2822873225113 +7.26282968661698,5.24139631405578,-47.3295172895749 +7.1824578843953,5.32506268109903,-47.7183412101907 +7.30945793340172,5.20987424643864,-47.3399364785108 +7.61940843287201,5.45675711889046,-49.2615508697843 +7.55697040242556,5.36142976880803,-48.3051577808701 +7.41402806787331,5.27753824683793,-47.580646708387 +8.02519328243236,5.71252970954105,-49.402569392474 +7.93797997282096,5.6729791751583,-49.51095719514 +7.90321800449355,5.64659716626328,-47.952620977647 +7.84660908010412,5.66230258724077,-47.3562672214878 +7.80624450542557,5.53314769227047,-46.6389837333026 +7.34992062178332,5.37796173533229,-48.2063237022498 +7.3616680926529,5.27040982735946,-48.1853800089051 +7.3402862760378,5.25261549110401,-48.6384825652865 +7.3839391085084,5.18007822317821,-48.908804550735 +7.37378932636905,5.38017578875085,-48.8083167687216 +7.39489704355839,5.18409088119399,-49.018406767836 +7.42804187805693,5.14740564107352,-49.5786413661041 +7.45560688028398,5.18671555185442,-49.3158043395925 +7.50359081395535,5.13670919228484,-48.012399731877 +7.54172552842344,5.1065529101125,-47.2752047831678 +7.70491316506201,5.01380936638544,-45.8062954098457 +7.47398832722932,5.16386415969719,-49.0056606106796 +7.82311895740682,5.06225324608908,-46.095560756444 +7.94746518365484,5.2258824022827,-46.9020078180785 +7.9945793462392,5.31062149530016,-46.0304847714565 +7.91286822667338,5.15323513287615,-48.5138388981715 +7.89455771379061,5.10790203324973,-47.2326657149183 +8.77022282405173,5.19785998691845,-45.6796258599606 +8.76459808843128,5.18639730558583,-45.5191662916713 +8.7612540064742,5.17587753336294,-45.4108638232349 +8.78034170495398,5.20896424178588,-46.1881438862577 +7.72337194190154,5.39784314855519,-47.8392438940888 +7.89697796663587,5.38721388447792,-49.5825025235823 +7.93506667554267,5.35592453025127,-48.0792859066889 +7.96863574522875,5.44209795916721,-47.6542788486503 +7.9420401661918,5.39112914453573,-48.5308491727388 +8.01634736881377,5.41452560091579,-46.9086452843991 +8.18053576870203,5.58519853927467,-49.6442603878376 +8.17901149130255,5.62802124106941,-48.6362454575869 +8.20085741168094,5.58949225842748,-48.9589209632575 +8.17364264970873,5.65823538457711,-48.5085631365369 +8.16272680345926,5.74918100497797,-48.7524633972289 +8.12138444299878,5.76427388995811,-49.0983190791018 +7.99793719393365,5.43602205157091,-46.8356406412358 +8.16493444121704,5.47333094009399,-45.4767911480152 +7.97683119197996,5.49247252563374,-49.3425358460975 +8.18811668828559,5.22683358001271,-45.1014051259388 +8.18772743160483,5.3391401153434,-45.6615652650079 +8.18974354921523,5.42590306926787,-45.1182637677218 +8.1159002181775,5.3370693422998,-45.7428280687492 +8.07448203386416,5.55861529724065,-46.9308405613925 +8.01194793157118,5.75912319134217,-45.5562902115872 +7.97107441882,5.80999736561422,-45.5735412452563 +8.05976260040495,5.70802247748008,-45.9487852265121 +7.87164245515737,5.85617446849715,-45.4635761862032 +7.84340721845474,5.87124137865112,-45.6539767818373 +7.923700136174,5.80372742345806,-45.3919596247721 +7.84880780922248,5.82776850610722,-45.7947965852023 +7.86861030834834,5.81223185282103,-46.1874788319024 +7.9140938401245,5.64932494057874,-45.7339270483054 +7.90214959220658,5.7026281589426,-45.3967843546078 +8.10535310922953,5.44007116635514,-46.5784038630997 +7.9073461848207,5.57555789300229,-45.6462837192968 +7.90844574235997,5.59971262811706,-45.6899807956983 +7.88913324913988,5.56622322320875,-46.2702903338761 +7.77210255617774,5.47477278830376,-45.5518749769659 +7.64259774782683,5.37415571079203,-45.3544447020119 +7.81799935657975,5.50116277987459,-46.462394092998 +16.864521911963,7.64300214404055,-45.4789853183209 +16.7282901791711,7.68344798507281,-48.513257480536 +15.7520098458248,8.90115286226416,-45.1569373956836 +15.7989174062796,8.84143216002026,-47.5441926743967 +15.7872919395163,8.7796053349127,-48.913512057526 +15.7989654552103,7.9312573226754,-48.5927377318376 +15.7291089828046,7.90917426904322,-46.1647172517591 +6.69984057899814,5.77900802248917,-47.7205030826358 +6.7204947259574,5.45439386568976,-46.3445824958255 +6.69118658665685,5.80890544184976,-45.5190140641895 +17.5454636637313,7.89319762793482,-45.0618588127217 +18.8602294318183,7.6923571196591,-45.6730965641587 +19.0146089197826,7.65682719483446,-45.884820559005 +19.7165914634002,7.9988561841145,-45.6298732554693 +19.8657633465311,8.18510049303408,-45.1166951950053 +19.9875546830137,8.36618603063659,-46.4144985138894 +20.084975104082,8.39566003071771,-46.5407024824256 +20.0521223022008,8.39993873831887,-47.7216226067236 +20.058740981593,8.30847030849278,-46.4157341616646 +19.95995158059,7.96868869264748,-45.0672130491585 +20.0132215931561,8.0799025346907,-46.2062250990216 +20.0162494353035,8.2191127113398,-47.5520155272619 +16.6621144173262,9.30040926957844,49.8782488091779 +16.5791575817787,9.31503054849937,49.2081688635649 +16.5225459945516,9.41621996255135,48.9824799163251 +16.4556037822175,9.4633671011932,49.5920127419864 +16.9400178979915,8.67758731427324,49.5001069428027 +16.9355074392202,8.64088523323241,48.7101629712787 +16.9187543976224,8.4341463711263,48.6702616055658 +16.9453275856852,8.5807959050995,48.0494107816315 +16.8834297283507,8.32036216908003,49.699986168666 +16.9910518067335,8.65670079662962,49.6221368640944 +16.9770469109972,8.74185747197453,49.1553010308999 +16.9843817893427,8.84573987822657,48.8309522234713 +17.1061330409923,8.75366697651631,48.5374877417884 +17.2243965507401,8.71068179645565,47.9444871279938 +17.0363606641618,8.81963389356322,48.8953583281436 +17.3759321829685,8.6210034578493,47.3166392678839 +17.4948321939602,8.56489233469303,46.9217261949992 +17.5819096066449,8.52830157988606,46.1991661940187 +17.6856258408438,9.35102154517361,45.7556363754048 +17.7002573089086,9.27201401379019,45.0813267355664 +17.6936634230491,9.42208048279353,45.3252727446346 +16.3739355552217,8.23438228017551,46.2346997517194 +16.3351135614904,8.33686902669854,47.6232988549856 +16.32045771727,8.49195529093201,47.4093381109922 +16.2744136217273,8.51900696001894,46.4196748588677 +16.7836937442402,9.05991210066214,46.2593665927594 +16.7162720511334,9.07675171514024,45.3845795657583 +16.7830401272764,9.03726290385649,46.8382364673902 +16.7933251843894,8.7892571075694,47.7842526787208 +16.8394728976047,8.56814561582533,47.7412525266045 +16.8711707105938,8.46788183253822,47.8788626023436 +16.8955464505035,8.44136738660428,46.5749721882398 +16.9162249636647,8.51906039677111,45.654808089515 +16.9045340717445,8.66922180244865,45.3129482538443 +16.6362793527361,9.13768628709374,45.6417919854126 +16.5170456871322,9.20357175821793,46.5922101713112 +16.471411377929,9.22051020313417,47.5573035433722 +16.4502724499663,9.12753436959204,46.7230491904356 +16.4092841609515,8.96595196367214,47.2104622012864 +16.4270473790113,9.04203691467713,47.1434386523799 +16.3967962268512,8.86652415068582,46.7095486605838 +16.4141720657846,8.78314184112676,46.2480463049504 +16.425393589001,8.90074207871259,45.9296590796061 +16.4256245559056,8.85912870916014,45.7636983217325 +16.4070199140346,9.135211792599,45.566636226161 +16.6619193794469,8.90923582074543,45.6211547495448 +16.7336778183949,8.80953297497701,47.1010494864794 +16.8218304887474,8.79912448467926,47.4465892876858 +16.9614932172719,8.87234611156986,47.1564723210636 +17.1514731073608,9.00370092557773,48.2269964408098 +17.0376755687733,8.96208916597804,47.5680605173364 +17.246998078191,9.01616552658641,48.3350927975813 +17.2993925046657,8.96371318167879,48.2790799767615 +17.386366937104,8.89334649844286,47.3622228426478 +17.5553576777018,8.74827035916408,45.0195562238146 +17.4725978615571,8.77939393080942,46.6796601651654 +16.8878638930804,8.8344069840675,47.643037113759 +17.8097016328392,8.82807204044177,45.3628257883285 +17.8730298218842,8.79751130127315,46.5065719864634 +17.9488678347584,8.77217211391539,47.3770580125588 +17.9616925823721,8.83755302924138,46.6520456522203 +17.9841754296887,8.83556319052526,47.3655252761759 +17.9757612626212,8.71730844602938,47.3984332706995 +17.9547587137237,8.74883859932787,47.4421990721174 +18.0000037478721,8.89191826926042,46.6930752632303 +17.862153849917,9.12931228595876,47.342678912457 +17.8937456831289,9.40856457092314,47.8216792700786 +17.9074372478203,9.73944117758934,47.9994514535233 +17.8898795734752,10.0836176664171,48.8060289075266 +17.8674881367284,10.4021169110336,49.9565418665454 +8.90402686037091,5.78244927224917,45.8231389922465 +8.92816209561903,5.76083082224963,47.192429779642 +8.91768914168061,5.75073403971407,46.1660065317234 +17.170149990388,9.66460435040453,49.7556716355878 +17.0603842267576,9.40975466753146,48.6221795766264 +17.0285278216817,9.08297180739103,47.8620015169445 +17.000682532252,9.0010034368014,47.3868988590212 +16.9354235370705,8.9981347738526,47.1654353882133 +16.793752468028,9.2284993132164,45.7886006114425 +16.8862294353369,9.10637023014106,46.318145485371 +16.7314762850119,9.28066213575686,45.5111781638901 +16.3476009819018,9.42785941067061,45.3120683189206 +8.95159525596318,5.73069714235584,47.0223452631926 +8.94706126647147,5.7382650026699,46.890853592272 +8.97702794732113,5.64206337134487,45.3561743609555 +16.1820819378835,9.42938565723011,46.2882424167387 +16.0369088591656,9.39663503145252,47.1885979795892 +15.9094020144248,9.40656795376168,47.1445199480797 +15.8360818380725,9.43370571589204,47.2160967117323 +15.8645953555319,9.24918599365574,47.6453132080921 +15.8925575636633,8.9204996924713,47.3981270122703 +15.8589726242875,8.64812831244415,47.8975693747669 +15.7688939556627,8.56783449596552,49.3480547765448 +15.8166965885792,8.49572536550173,48.6320195098222 +17.1695392328361,6.50247493833555,-47.0019765181057 +17.065674455705,6.35671604711733,-47.7300939064469 +16.9347419860539,6.25960392682935,-48.7247671265525 +17.3541946364347,6.48968050842285,-47.5799816331852 +17.2419937090054,6.5077879027029,-47.3225431102447 +17.497171573385,6.698140708778,-49.4306580864989 +17.5719115154382,6.93235772268245,-49.7510813691423 +17.4226078273464,6.58090067768306,-48.9741903089735 +9.49615441593656,5.87545330768419,45.1299346770197 +8.85342185632659,6.13058532046335,45.2248706861884 +17.6405011637761,6.35029220284667,-49.0995689595105 +17.5329627341457,6.41786281002016,-47.7575306401491 +17.4441448588091,6.55112189035302,-46.7887836549302 +17.3024534271966,6.65014075459124,-46.6076202427451 +17.1160111021021,6.69806961057472,-47.7328443471454 +17.0489067649389,6.69067244806886,-48.5677179801185 +17.1835216373413,6.67872150066199,-47.3239609373475 +16.9664190653758,6.64871139498264,-49.5280079429475 +16.9194560518215,6.54701002993352,-49.8521317352878 +7.63933758969527,5.81696655723019,45.1961801528237 +7.58799391720165,5.71911759489828,45.5422953309707 +18.2732882583499,7.99521675835314,45.8329363982841 +18.2804229713368,7.83009472196976,46.4864020175444 +18.2813529213333,7.93220773446762,46.2609709637721 +18.2913834821129,7.79494318764199,47.2333597657019 +18.2940247264505,7.7210309761875,47.0476939068442 +18.244351058226,7.7520354603564,47.4590735026133 +18.3370078869288,7.71780113196372,47.3928327676542 +18.1453543163806,7.74046594020335,47.3756269596084 +17.8596236158433,7.5673760953583,47.995720170486 +17.9927965750358,7.65425778114167,48.1056804962717 +17.7641169394033,7.44872009953586,46.655770358287 +17.6546832093371,7.42273823074194,45.738971723347 +17.5836589474274,7.34658463686454,45.4984310915475 +17.4744736321637,7.55447289319279,45.0042297112915 +8.07603270354604,5.73111641009831,45.5733798533302 +8.0385681432572,5.7225007767836,46.0241920413109 +8.05919188185742,5.75618078346229,46.7857344970312 +16.4079737243669,7.08745495245164,45.4906501554883 +16.3838188719333,7.15662540286375,45.5426325124143 +16.4243960881795,7.28088322165582,45.6870798172711 +16.4596082554759,7.4679372138843,46.3944748795543 +16.4638374527122,7.70173308163512,47.5529540585032 +16.4762580731086,7.76269893560209,48.8797792580353 +16.4592511666499,7.76842089198492,49.3780707825024 +7.29660675437847,6.13528742296888,46.9366740272559 +7.23227369231814,6.06016276218423,45.5191874008977 +7.38436813127461,6.12893711933349,45.0438647332983 +7.34704978059985,6.15805091408042,46.2983398276359 +16.358217715199,7.34665691026685,49.0803306037831 +16.3258701810136,7.16721990707987,49.7615530268171 +16.3536736102318,7.6021138740647,48.838020937063 +16.3440315871693,7.51436648427446,48.9730890236681 +16.2783818928731,7.76813459446593,48.0451842407194 +16.2185443026988,7.78941189056565,47.8507884581714 +16.1266280296999,7.65533496225255,47.6334701585458 +16.3018920015388,7.76751889643995,47.7455834496438 +16.0581983519617,7.47101505255579,46.8714694542564 +16.0698138945532,7.56603119848688,47.0499295310221 +16.041829786529,7.35978106092267,47.1103452236092 +16.0930410605521,7.18018302526509,47.1349059337874 +16.060613927548,7.3399630895484,47.0742532351355 +16.1634140699421,7.13160410037434,46.7613064246864 +7.33565271221254,5.87271464091435,45.9848910546694 +7.3182631720783,5.74017166751565,47.6672091295379 +7.35862085603699,5.6447375469041,47.6229874169853 +7.33915630023626,5.72549512358784,48.3813460955466 +7.3556180786452,5.66655460042504,46.6641742724186 +13.5398671071008,7.98278541420885,-49.9587512912129 +13.6077937569813,7.9033846994001,-49.7601429918021 +13.792483433344,7.79153369716775,-49.0547561702845 +13.7248306418194,7.79288042861725,-49.5209429933458 +16.4090083642652,7.05212858551344,46.3343066260552 +16.2790090209823,7.06101203868674,46.0175039851666 +16.4597602127162,7.14865089773915,46.0810273750866 +16.5177459389786,7.14439680250539,47.2313532343587 +16.5618979388441,7.32061995523641,49.4446764294174 +16.5333669594967,7.2430992536796,47.8949899573341 +13.9097809201973,7.64468335636009,-47.3938053904162 +13.8196551124975,7.70117499407393,-48.4569630411898 +14.1985865196338,7.68967455816241,-46.4646584300192 +14.3860126037784,7.73773948441509,-45.8903057389923 +14.5788799410932,7.72574909537257,-45.8172174242169 +14.7398289380379,7.70960395427972,-47.0978966663526 +14.0526101568196,7.74503969719928,-47.6258917897463 +16.8669128090349,7.30742730984122,49.2527164836702 +16.780540557126,7.18603569438236,49.164076311361 +16.694255478664,7.15372441504089,48.2066790309066 +16.5734250809732,7.11753521706458,47.2943466840471 +16.4796159485621,7.20213613838538,45.4117236523895 +16.0953088193717,8.44075201236545,47.6831296666864 +16.1296071558997,8.50270679063541,45.7429124381895 +7.91637673367003,5.82145113481071,45.421368246684 +7.99218431192527,5.74524646693758,46.5649893464867 +8.06729893475309,5.69886307494697,46.0483437822473 +8.14043396381199,5.73008989468248,45.6139391799814 +16.8782111893509,8.54352365703406,49.8524414831745 +16.8394909229534,8.73883988309952,49.5381265414599 +16.8217108059989,8.83171125848717,48.9774092967174 +16.7959483181914,8.77106212114398,49.8489511348951 +16.7070929739684,8.61181341962453,49.6469283784768 +16.6533923252988,8.4218925293609,49.423276390322 +16.5970121923132,8.31044840449695,49.3257912643886 +16.5576751698125,8.20617070410803,49.1415408671718 +8.41645513819117,5.51678715274753,47.0699017838807 +8.39971545086299,5.49865864920349,49.2050651591594 +8.40972976291591,5.58267216924335,49.7785456293798 +8.40480836950519,5.60798607815343,47.0952743928913 +8.3656342230598,5.60981184286284,46.370902263866 +13.4192286987666,7.73724664313234,-45.1356598822913 +13.3199132253122,7.67310117484653,-47.2860987539767 +13.2717104370725,7.57677549063446,-49.3803636305352 +8.40016345010835,5.60488095192329,48.80133180345 +8.35661993551843,5.57762920936074,46.1476510710673 +8.30733189221197,5.5790582448233,46.5191395624108 +8.27068636315442,5.55966886794734,46.2446772766427 +8.25877089551422,5.56156514605583,45.8720537826115 +8.20097631707565,5.6470749618696,45.3030198699161 +8.05930420879713,5.84052395555282,45.2892192390526 +8.05409108129762,5.91644249592589,46.2673379118525 +8.04701895505776,5.95392045855286,48.033011355547 +8.05908653405733,6.01163843377996,49.340079506606 +12.9738278329582,7.61925479712962,49.2575521680932 +12.9448494072378,7.63582330217813,49.9789093125154 +12.8970915963334,7.61978156520994,49.7700245667619 +12.8189636889306,7.64335002764466,49.0668863891774 +12.8392248142291,7.69513756823613,49.7453085746952 +12.8288328715752,7.68483807078393,49.7953047301512 +12.7737729312455,7.69598302195215,49.6662398546665 +12.9905747297362,7.43690546891267,48.8473461958168 +12.776411850679,7.76905243956,49.0692858061785 +12.8474630553458,7.78817042801006,47.6843864822954 +12.9714088772622,8.08829627452603,48.9765980704348 +12.9079843153464,7.83856473801495,46.871399460171 +12.9421575412249,7.92391523600268,47.5226093082463 +13.6757624863271,7.69301821595405,49.8279545350042 +7.91161625515167,5.85716576326334,48.5827904168997 +7.88015453389696,5.80663745062185,49.7609533651475 +7.93918010498466,5.8608672050228,47.694494684308 +7.94345434580921,5.96987865010335,46.4516955002187 +7.92335874807355,5.95012337349512,47.1560637695649 +7.92147644300468,5.96688831066361,46.6677052512001 +7.94497910605406,5.9832862478791,46.1459995138088 +7.92738512652139,5.89391597733962,48.277311824441 +16.1581575019681,7.44974729028351,-46.1407266426184 +16.2087807247828,7.35774530502808,-46.0985560225838 +16.2658648670859,7.39519317164007,-46.0305595117775 +16.2875540530893,7.4986591491977,-45.7277668692047 +16.2510788049127,7.79372876940819,-46.5453612077901 +16.0981068343137,7.58406514588539,-47.6091299717858 +16.2025963963702,7.70140756252944,-48.0521836642686 +16.157998431717,7.66302283313731,-48.0296125858272 +16.0356709373732,7.54179349199736,-47.4574257467636 +15.8957300865972,7.37838952668517,-47.7127832515911 +15.9758415698796,7.51317783460498,-47.2739592035498 +15.8348673323442,7.36081795417261,-48.1850592981185 +15.8010259517757,7.33864147438807,-48.7897986076513 +15.836028821672,7.3987871722468,-49.5122781151707 +13.7046622797121,7.60146897589559,48.0186124801875 +13.7353434833208,7.52568341105804,47.6304710420387 +13.8363540053121,7.52172123967235,48.4540499904865 +13.8106823324007,7.63058299207113,48.5114225617896 +13.7640799689316,7.73682316432167,48.4778884597995 +13.6882203531202,7.81984909849172,47.6070991224233 +13.6701080337813,7.85391614549801,46.6612303145057 +13.666739937926,7.82269837401608,46.2399371087193 +13.6374212596326,7.78387605045478,46.0851879085862 +13.6192715642306,7.72524708396608,47.0941046902541 +13.6048988073206,7.63363577572913,47.2952041895724 +13.5882330736397,7.56625904680502,46.7949570780769 +13.7066848542674,7.55194792414258,47.1755695319621 +13.5889686299988,7.59629574897721,45.5429238529211 +13.8047218850924,7.49989607086925,48.535743269588 +15.8627730903266,7.40713369853288,-49.7130717522137 +15.857473319619,7.4173482038765,-49.7883756170227 +15.8769477580638,7.36062545500818,-49.1481281580834 +15.8721103273481,7.30151252132956,-49.6946410508397 +15.688634471894,6.88509626891183,-49.5079311883982 +15.643895248759,6.96523942435365,-49.008564513825 +15.6276864308611,7.05952224056294,-47.8658103707302 +15.8490780057181,7.41724922089041,-49.6188394525073 +15.5961783294532,7.18695681921759,-46.5419357919151 +15.5782546766971,7.29498210960582,-46.1921292660866 +13.5359557147257,7.72852405929461,45.3269837259006 +13.521381857106,7.76128517487014,46.0150913868675 +13.5195579566843,7.68475370484083,45.4688689691725 +13.5131259760528,7.78327038623414,47.9184437346466 +13.49829131278,7.75194688107356,46.8829609582697 +13.5503573343579,7.80162816402057,49.0062789947409 +13.5696325567854,7.7453308469604,48.7010284080039 +13.562338864584,7.63667165539349,48.5205336362157 +13.5411890572888,7.48130719945304,48.1670081560069 +13.5348735384618,7.3660395832513,47.9179542880249 +13.5321217723504,7.192018439696,46.3612791340379 +13.5573879776113,7.07542946860829,45.5641374137362 +13.5847041762812,7.05494057377323,45.2033337118695 +13.5276514759356,7.82007338913904,48.9004316314926 +15.4334816016145,7.16734882872307,-47.17372943463 +15.4206362857277,7.2491775391914,-47.6439623871442 +15.4162105905012,7.29040481130586,-48.3896323022508 +15.4928435273197,7.18527829235818,-46.0149579914276 +15.4121202993777,7.33950131438064,-49.7643554915314 +15.3823713923124,7.33856100676233,-49.5475466194082 +15.3370482588869,7.34346822428932,-48.8000977540572 +15.2831909997105,7.53284647185982,-49.1944800493413 +15.2589145750405,7.57615774263215,-49.6716597031012 +15.2225787380115,7.62974450267148,-49.7235939111369 +15.2146437461202,7.69808648508978,-49.7844632395333 +15.2612220226223,7.80157573672092,-49.5656981192161 +15.2226877699087,7.75862121243542,-49.3256675728094 +13.6181812155321,7.06679350674057,46.0807788108753 +13.8232958849205,7.14920921105985,47.9162256278139 +13.704751549392,7.15303268750209,46.7229466468442 +14.2521363075836,7.69105721526862,48.7882237120672 +14.3246759295244,7.80870621596174,45.0819860110135 +14.2906515162439,7.77860011371004,46.9612461847564 +15.2307576290037,7.91475932298344,-48.3339612133113 +15.279029148859,8.04067791043703,-49.5408899101689 +15.1710225765368,7.83349299891205,-47.3272202091232 +15.1123899804938,7.89020261173438,-46.8856820179725 +15.066783808968,7.88001400842535,-48.3005275842126 +14.9866418029684,7.90596735342202,-49.6575929704812 +6.42779349160562,5.69694915466329,-45.598721302564 +6.43696089643755,5.71303281071897,-45.9607418696278 +6.45029733739957,5.70974219581165,-46.1889310876205 +6.48619729296282,5.69461433815398,-45.7595547923772 +14.5743005029703,7.32893599951801,-49.8830623952359 +7.93692158297719,5.60150632365887,-45.4833508790065 +8.02007329297165,5.6731386182733,-46.5619763602801 +8.13549433735772,5.7091687367647,-46.1793280581711 +14.5003124840244,7.21386654716304,-49.0498126114773 +8.25462688928407,5.70947098699852,-45.6069518603736 +14.457492815016,7.11338957308332,-48.8905337199758 +14.3911551521984,6.72961939748612,45.6397559891275 +14.264874611929,7.12161240694507,46.1815683548418 +14.1909932610432,7.18226944724082,46.138537685836 +14.3311140613275,6.92979083843872,45.5947438861871 +13.9885362818134,6.97355120137387,48.4091150691033 +13.8623723113445,6.86357611275159,49.0434442834958 +13.7832293145088,6.98514710572453,49.8164271441046 +13.7296204321534,7.18533602964244,49.9849820044309 +14.141672563213,7.15815199479461,46.5903428642345 +14.0736185425683,7.09286927723433,48.079657571975 +13.939939851997,6.89163385554256,48.8562288822905 +9.67006903273955,5.2848262093003,-47.1005700895962 +13.6924707634752,7.40188043928574,49.7327411758079 +14.3631640920372,8.10323064455097,-48.437581350052 +14.3629436740521,7.99343721954984,-49.5442326121449 +9.23656453850763,5.73402027226696,-49.9768695220693 +9.06815704829954,5.76734303311613,-47.503944029257 +9.15749182888535,5.79811100562064,-48.5286387993882 +8.9720794768371,5.78426295153645,-46.4485950778583 +8.87889922684798,5.72773803405097,-46.2251738361545 +8.79260669781433,5.63348473145494,-46.684786286679 +8.69467817048594,5.59073264972586,-47.0162762835563 +8.48421256425279,5.58926529316191,-48.9184317061741 +8.57883490492748,5.59420647955155,-47.9314324668487 +14.354269450753,8.13431570970593,-49.3454351709705 +13.7773885059728,7.6693399625631,-49.502940418455 +13.6563165640268,7.64327606774998,-48.8780495792471 +13.6182499378775,7.58249103441032,-49.1375891029284 +14.4034898994432,7.26651906606347,-45.3651444103898 +14.475149094897,7.35762994278577,-49.1068669701244 +14.4216188632167,7.32516520358157,-47.1163686071049 +8.17772286668886,5.34498012530162,45.2552269170337 +8.28878828018546,5.44817375976239,45.1169898592452 +8.08130965675438,5.36138772952114,45.4701503800185 +7.0304453012846,4.90735949170532,45.6076136158198 +6.99527723730349,4.89303407610653,46.4344939279835 +6.96280016751948,4.94627820097485,46.535286721351 +6.94795362035048,5.01151005755899,45.9239083196463 +6.92252217639544,5.09314119989515,45.0755382293023 +7.0001319757123,5.45697073653538,45.1018339666627 +6.98715513784923,5.60866338384351,45.3842559163646 +13.6722391731401,7.92279321124127,-45.7942074642344 +13.6612264513108,7.99655670186279,-46.5815808039085 +13.6881365928244,8.05675631439176,-46.2415865025538 +13.718690458991,8.0286338336278,-45.9676838515181 +13.7657609838727,7.96716431920161,-45.0140971460416 +13.8149645338584,7.89274239367202,-45.0634143436942 +13.9785217713135,7.62509114427509,-45.8787837461098 +14.1295597648793,7.53342706462159,-45.8114863986683 +14.4196390434314,7.49568457134827,-45.7978172382195 +13.8860131418413,7.6472853942709,-45.634474841185 +7.0099375389405,5.74818843632036,46.1596279159055 +7.10312401383995,5.75970712627378,48.9268583635462 +7.62282006546064,6.01411646839361,47.9176975113263 +7.71312943924075,6.2301209981407,46.1871290149808 +7.78346571592574,6.33117904960199,45.4537510209765 +7.85558720769995,6.21638041930751,46.5021536673787 +7.85991080290931,5.98529650990246,47.2367605453526 +7.84652360769809,5.68354454415719,48.1944963966702 +7.80074806080905,5.37352304213385,48.6519258661415 +14.2936643537347,7.49983343507399,-46.0735968116964 +7.07686801276748,5.80639609058468,47.2020736186245 +7.82955700925485,5.20869675298256,49.0078626058484 +7.86018928179959,5.16633063148363,48.7867087763187 +8.29450620277475,5.81589264556774,49.4834018468001 +8.32004663831437,5.87027222035338,48.4167720084978 +8.32737807940228,5.87021102906405,47.7845602116275 +8.36872971309645,5.82957580276901,48.5253126051975 +8.45255078462951,5.80006218321086,49.3449681391564 +7.84534000520022,5.09319928666923,48.8284413892151 +7.87006600910023,5.20730100472914,49.5402729173496 +14.5692789436894,7.46319686716124,-46.3449079533126 +14.7606660078618,7.53956664162376,-46.3892335553172 +15.0813398636513,7.5717791973914,-46.5764529813404 +15.2027311830668,7.57181254374541,-47.204182658949 +14.9392123764829,7.55833418267619,-46.3274942254669 +15.2845746903282,7.47978997648985,-47.448427988422 +15.3892203942655,7.49391402831439,-47.8553492387925 +15.4979780278769,7.35002455972308,-48.7063556019935 +15.5755301528405,7.28306119327727,-49.1614101233796 +15.8099144888797,7.11284836984873,-49.2283372674366 +15.6978165760117,7.18721248575502,-49.3108781362783 +16.0608496533698,7.09396056878694,-48.340190882306 +16.0984135433745,7.13056173211198,-47.0623463925803 +16.0950015597972,7.15352504180092,-45.8863086762916 +15.9194504688793,7.09511385006873,-48.107391826324 +16.1354829352567,7.30468642664359,-46.5737253120929 +16.1221391043696,7.36440123625705,-47.9983922495902 +16.130576799354,7.18839122729573,-45.5481706201468 +16.1018355965311,7.35700320213232,-48.3770549140364 +16.1279250837804,7.43875518262136,-47.71134676921 +16.1056375793388,7.40598890199384,-47.8866416398256 +16.1929920210425,7.54842917063436,-46.4394015174465 +9.20794909116088,5.67496524754328,47.0171888002037 +9.22967081520309,5.54558371085745,48.8778785704786 +9.30645935771309,5.79857634301869,45.4001922812618 +9.24713777378595,5.77256458209624,45.4677122115506 +9.56776152671958,5.83638180642506,46.9021540620395 +9.43363969955965,5.81297716596679,45.8280169610469 +10.0448130122452,5.78127211742042,46.0800891268283 +9.79031816906718,5.80542383828007,46.7588089185006 +10.2656313234057,5.7873888114372,45.2067573813883 +12.4524064737627,5.60363123875415,46.5369531797056 +12.8137704440392,5.5838419578313,48.7801147088638 +12.5756049446645,5.24805381858495,48.9311024731564 +12.257916271037,5.26227555690455,48.1126883451037 +12.3971323251668,5.21794611130691,48.8133035018441 +12.2295128674786,5.2799576922212,47.9555478852096 +11.9669628005919,5.59060904025092,46.9406531366758 +11.78993274956,5.69312397137799,46.7798518932743 +11.649212645396,5.80899961771631,46.3711879853475 +11.4913167652272,5.88420956278104,46.8090203726827 +11.3375100116525,5.88858645441075,46.4913474658525 +11.1659175048685,5.88272000169501,46.6112565605947 +10.9827382646838,5.81299672895076,45.1961873674413 +12.1456162512228,5.41536085015372,47.4015606947366 +15.9279343272318,6.51249670597625,-46.0246521291401 +15.9566247290431,6.67529508600059,-45.6788501190315 +15.8643417517933,6.5748152018152,-46.3121707892368 +15.7799731326339,6.65504101486873,-45.9778003323323 +15.5337510972595,7.44556735534679,-45.1756506407942 +15.5027430545827,7.52262347810694,-46.7052981464066 +15.5452423888117,7.4610407504901,-48.7428738268624 +15.5994989732216,7.42567568291323,-48.7791118062894 +15.6694698411489,7.35098624566917,-48.1278385834617 +15.7763653078456,7.31579841058888,-46.6356699820261 +16.508055710402,7.06637116479523,-45.3641675127729 +16.5706872437542,6.83674773081678,-46.2550684530185 +16.58769592649,6.69585068297172,-47.5378656343025 +16.5785325825449,6.73163076231673,-48.1608009235211 +16.5864253656879,6.78395293149831,-47.4812740445051 +16.5525515832128,6.78810290008759,-45.755362449753 +16.5643719430603,6.79939735126675,-46.8176469072266 +16.684875203909,6.3250985550486,-46.3776945920201 +16.6839289877513,6.15470465614011,-48.4212798151641 +16.7016860187846,6.04536469920981,-49.0679544244618 +16.6841474126653,5.93461274367269,-48.1969744013515 +16.6638807054547,5.89961577789071,-46.03598198335 +14.4115597073604,6.2211954908108,-47.7224120218656 +14.474548873372,6.32501427461574,-45.7679142075564 +14.2772533165485,6.19132895571035,-49.1238799479918 +14.0559968616,6.19724861549262,-48.7312310869659 +14.1673483085592,6.13607158624281,-49.3855962635635 +14.1076764853251,6.21770743555635,-48.5307006559092 +14.0170603939612,6.12736259268354,-49.2744593090659 +14.1871890771755,6.45730566934071,-49.2992247114454 +14.1724091437886,6.66999277887891,-48.3844330432854 +14.1747996060896,6.97222885511844,-48.2503393807098 +14.1591536053347,7.14281422010692,-49.3095832394042 +14.1977684388413,6.21338707555299,-49.1837862793522 +14.1526799746219,7.35559554728818,-49.5185432453492 +14.6108616958082,7.67145988624024,-49.612255522963 +14.7038672749182,7.91975619170088,-48.9145332413938 +14.7450472030259,8.06455752499585,-48.0213837197894 +14.8141887880007,8.1297170468378,-46.9594950448728 +14.8686888003788,8.10568840279328,-46.2287158152937 +15.9634301231123,5.78369259334189,-45.5227984503804 +15.7431725179743,5.96986423094229,-46.1731446338702 +15.6088013289797,6.01041243058677,-46.613607792015 +15.8925900849318,5.94256399250127,-45.858990139393 +15.5311700397564,6.12426776039872,-47.2747935174952 +15.5248516774405,6.17779295874293,-47.9532175088697 +18.2675371912058,8.07742761880008,46.8271094630572 +18.3763883409065,8.16360392514888,48.7067240431779 +18.1308720595981,8.17788442818527,45.2251945219078 +18.4879610377436,8.31528690489019,48.0033453225091 +18.4745735926145,8.23361239482547,48.969775542143 +18.4822082452976,8.31699218800108,46.7585811098954 +14.9864777015309,6.80486902459654,-45.5871680849931 +15.0568982798948,6.65186271767723,-46.7687193863785 +17.356862633228,8.10157115044456,45.9214541825071 +17.2222469284452,8.17631131663835,47.0116666982409 +17.1250779831231,8.21408975300831,46.8514931443706 +17.044631930229,8.22188215791672,46.9444171271069 +16.9705595270918,8.05831353586012,48.5948346532552 +16.9981599848182,8.19129101425482,47.4929767266284 +15.0912634214587,6.60863478025061,-48.2189576663988 +15.0757292302458,6.65587028575723,-47.5552948135869 +15.059034903902,6.69211007683523,-45.9529257942513 +15.0622762342572,6.96197700975274,47.4112327010454 +15.1018214172105,7.04639674367883,47.4100001840814 +15.158967535309,7.11469274700994,48.2905770940236 +15.1836681705845,7.10849469004206,48.9249281016449 +15.1659255394518,7.08097130967575,49.8740339626653 +15.0426894225252,6.92759463376384,48.8201623993536 +15.0991718205086,7.05006407573564,47.7218854042941 +15.294257576242,7.14700805480431,49.2705209306289 +15.274243980278,7.120003502784,48.1858414729521 +15.2516182045657,7.05727400269916,46.9325661040919 +15.2533974953912,6.95411867132476,47.0434990502661 +15.3012065190857,6.85554677975555,45.9359087330896 +15.2726059671688,6.84284654339402,46.4495835411478 +15.751785672635,7.14829213053224,45.1379716885108 +15.8673570926169,7.11086121797411,45.2417015922349 +15.6756313413227,7.23795761722837,45.5237044938816 +15.9753659960249,6.99926726398384,45.1288100111316 +16.0855098855848,7.17297870848345,45.247578379433 +16.0859573895341,7.24502667203781,46.6299907749381 +16.0722927595074,7.32342960771356,47.4510536836939 +15.9991218030009,7.46521311635022,48.1053122356457 +15.9217642319655,7.5665004516911,47.7959486463642 +15.8358411941024,7.72087191293859,47.8982219506119 +15.7628030935246,7.79303530860805,48.3739744870938 +15.6925507466897,7.83591284504188,47.8291173119361 +15.5919254214121,7.89402353758013,46.9207047648929 +14.9046794477744,7.98726519902644,45.0219145424772 +14.8923351833921,7.97275403302963,46.1835244850605 +14.9077002855416,7.94610157507477,46.3339669945375 +14.9003967943411,7.99012825429054,45.872938806276 +14.8883775000985,8.02087032740022,45.4448055427239 +14.6031593209438,7.62360096655995,45.9173951942239 +14.6568646170828,7.57706446673155,45.682977086406 +14.6587173240915,7.57751779180334,45.0807975048398 +14.665574177276,7.64949904374395,45.0723790066374 +14.4940163416409,8.47856166704101,45.7761446469982 +14.5197069133587,8.46980578688775,46.5723696325745 +14.4888908476284,8.54099558370911,45.5648060963998 +14.5481144633918,8.46317978861825,46.1865972832492 +14.5543948324603,8.48404362914903,46.8969707446188 +14.5397129551758,8.47745550345894,45.2553474140907 +14.8043921139361,8.19358419415783,45.6401953019254 +15.7053672161529,5.32681737568608,49.7122687943313 +15.7002939222236,5.28300324001561,47.0600472660371 +15.8387135529413,8.97172781534804,45.0942257806099 +15.8236798236539,9.07764201545979,45.9434240724897 +15.7745724769511,9.1554334188866,46.5967528382531 +15.6935573834646,9.16659480838149,47.2265556403361 +15.63646545902,9.1515991296415,47.1593991766124 +15.5767785082545,9.08593791278559,46.6895683671378 +15.5285350876657,8.99387900007488,45.909158257314 +16.6799824696293,7.31795695874372,46.5542132019223 +16.793216703846,7.27349558773918,47.0596867701903 +16.8948546824717,7.09879780061987,45.5236439959286 +17.4545614380165,6.83586353456522,-47.8970660833964 +17.5877429609983,6.70511845655389,-47.2831900688735 +15.9208670864934,7.00263914327135,49.6993448165142 +17.6762110446632,6.79930231033045,-46.692170913767 +17.9169464842776,6.84297009899333,-45.845473121732 +18.1059054707263,6.9245294023068,-45.8187241412603 +17.9908412896117,6.88817246133599,-45.8781437379814 +18.1615744948025,7.23259801353635,-45.4654673487248 +18.2447320636067,7.68362096138511,-46.9202347794826 +18.2219354739478,7.42214452588,-46.3462736609302 +18.303385177797,8.41207154942101,-47.6396546518188 +18.1486082463133,8.20451795824987,-47.8695710932133 +18.2322429128725,8.35819161536965,-47.3973174654195 +15.4782053220522,8.8797615910296,45.2642747659332 +15.2396706396667,8.42980529559496,45.0716687843168 +15.1263542237448,8.46977470389064,45.578217264268 +15.0053099927607,8.6325825624817,46.3146231015226 +14.890513752224,8.7770961502958,46.3520318054944 +14.8129286148938,8.92929991024589,45.9966220883844 +14.5988952075298,8.98885447830196,45.0118943162471 +18.0831889666581,8.0361048614861,-49.6569198326441 +13.5585021990119,8.51970243320814,45.047439396442 +13.5521275886798,8.55156631209663,45.0273066932368 +12.8773201283525,8.22419723376376,45.2368949855563 +12.975018215844,8.16165599385605,46.7631525851536 +13.0349381280062,8.16375244151037,48.5353929548536 +13.0871798786908,8.12442049932107,49.4287035292731 +13.1280113815553,8.08484851809188,49.2354018511686 +13.1761302029882,8.12620071309374,48.5384750682074 +13.2387394677329,8.23235276389407,49.0506194467312 +13.2868495584107,8.37281712948197,49.6163408487492 +13.9357662227996,8.13198841777193,49.8901093660154 +13.9414726669793,8.09122563157726,49.1351980070904 +13.9335123465539,8.07759726114308,48.1209080519835 +13.9764320392558,8.02988173076187,47.6008987029766 +14.0354614196272,7.9769161372349,46.8141491851158 +14.0991371284405,7.93392416081367,46.3187634357633 +14.1822777264971,7.93887132850923,46.9464533543488 +14.9624899537107,6.87821426407307,45.3424062054387 +15.3359783543874,6.99822489672699,48.8644396438118 +15.4106682578956,7.39014902881505,49.8673922696926 +14.258459465922,7.91134106168756,46.9680021481743 +14.4259107126612,8.11602455614324,49.1149081658563 +14.3692614677163,8.21689708050147,49.8416766821507 +14.3629070346529,8.19555008885089,48.8666118770248 +14.3417682431956,8.05670586330399,48.4515359409907 +14.2444392934459,8.13523977282959,47.5496662472071 +14.2425193355345,8.02621297033757,47.2096954506941 +14.2588875343542,7.9272374652477,46.5133598036419 +14.2726443862538,7.88688404075586,46.2207792351724 +14.2693174033727,7.87223524416553,46.1958107628785 +14.268625948007,7.70003942230425,45.911018912551 +14.260998951555,7.66932091648332,46.2481606502784 +14.2338167085019,7.60878437424149,46.0937960359793 +14.306182913629,8.20584798076655,48.3308009384536 +15.2274209944264,7.73185359759064,45.3877442823563 +15.1495808408603,7.5330720113572,48.0885473499739 +15.1156258321823,7.40816912367623,48.6215470292906 +14.9260749011737,8.03776127552567,49.1450740725862 +14.8910819460178,7.90923885679939,49.0291441571918 +15.030677062521,8.12992473448059,48.3522996084378 +15.1487991907385,8.07110469335821,46.8797808053987 +15.0677963045053,8.09998431962077,48.0603039933215 +15.2391675835776,7.93982447534393,48.9624881119158 +15.2072612943066,8.03936447230744,46.4556498144242 +14.2463307422683,7.72482402576131,47.7717920880739 +14.2256104382575,7.63930581759306,46.3139827911571 +14.2066680568787,7.69542744426268,48.0946248760584 +15.3101684885924,7.77105764861336,46.5837460938575 +15.3002876096284,7.71318728636764,46.6629411197413 +15.252920573704,7.6435526307633,47.0404575134309 +15.2680875666215,7.61095169961614,45.1137914879544 +15.276261831546,7.72063896817573,45.4802813994586 +15.3046645868069,7.85733486883172,45.2704015935487 +15.326397595242,7.97392716637921,46.3479126998171 +15.3439296327746,7.97162448012419,47.4017844123047 +15.3740189880502,7.91436006438647,47.8054334738457 +15.4035484231673,7.84946285020245,46.7552393367353 +15.3990252973199,7.79168091781926,46.1997398524798 +18.3153913748818,7.22505588649888,-45.5984014272223 +18.3849021561204,7.24711651189626,-45.4274361610305 +18.3057501055428,7.10814337095711,-45.8694891939853 +18.349585806216,7.0982529161015,-45.3145604299753 +18.3669266920342,7.57521492571298,-46.6020438553759 +18.3307343192026,7.62755967637043,-49.5945882158524 +18.3756387946519,7.29106200324207,-48.9454458794426 +18.3803951218424,7.02455545584842,-49.7273465706158 +18.3160118528168,7.69243672828469,-48.032489154587 +18.2556417517808,7.96405659173848,-47.7323844903127 +18.1608069456801,8.09683925947996,-47.1314351139877 +18.1206969471342,8.34263740022,-46.3916927168526 +18.3152072637883,7.95105341907284,-45.2734284981327 +18.3778782841846,7.64665943703515,-45.0515406725567 +18.3953806704651,7.50976474212881,-45.6000973488636 +18.485907732541,7.36132480234016,-45.430422920716 +18.4317143356481,7.38649283291799,-45.2157359831946 +18.5751808848066,7.65667279987136,-46.0166947116513 +18.7559534835119,7.92072193866188,-45.5712311894809 +18.6473315462422,7.79926967181848,-46.0513025112036 +18.5150457366427,7.45061266059785,-46.2583722085712 +18.8840590824962,8.01554073997585,-45.4710672572941 +15.8188861858008,7.26604041756453,-49.2296942396927 +15.8270547252194,7.23673674378064,-48.802420695257 +15.8796899740831,7.22483849728262,-48.2713339104841 +15.9709872519615,7.1953682913246,-47.3489389759523 +16.1034986126237,7.25057479268037,-46.7359158485884 +16.1858866507494,7.275429398816,-46.2540713182036 +16.2244984668745,7.37034164329522,-46.6640607863682 +16.2516357250122,7.436871017158,-46.9110325178694 +16.2661842525025,7.46227314544864,-47.612739921899 +16.2704469553202,7.54270612257727,-49.0842842890882 +16.2371635427024,7.51210395454438,-49.6018922528111 +16.2098085249545,7.5294338480068,-49.0289696318504 +15.6715606419302,7.54976394919005,-49.904352482401 +18.8107057816516,7.96683384970667,-46.4642302714304 +18.8514687568145,7.99123249373919,-46.1158892188859 +18.7209200177433,8.02780319556591,-46.4755145581734 +18.6230648717064,8.01238960259331,-45.8578539196323 +17.9226534247,7.75061305034373,-45.4810501618356 +17.8670967741762,7.84328204115433,-45.6339235050436 +17.8104227793892,7.84237221334558,-47.8154429206933 +17.7849481863358,7.77221608144101,-49.515460054723 +16.1809377227455,7.47488601592252,-48.4713436513205 +16.1076408869839,7.17446715471171,-45.6992572160099 +16.1479198859439,7.29567597203215,-47.614950562919 +18.8317887238976,7.06613862660748,-45.4280133340625 +18.6937746923375,6.84945476250246,-45.7041014404684 +18.5769610393822,6.71901613773367,-45.3039047841429 +18.787621020413,6.920136005123,-45.2025639544359 +17.108547310499,7.31456921754786,-45.802012029234 +17.0371163504429,7.25483482242258,-46.4799748767002 +17.0164917363061,7.19081292059934,-46.7066948936597 +17.0030469184351,7.18133435196337,-46.5589680369044 +16.9092071453146,7.15853030368158,-46.2924668845293 +16.8267293356544,7.07636378166788,-45.5884229300633 +16.7103832819301,7.15487927284194,-45.7870086406888 +16.6059378043325,7.26098018333682,-46.2859097083303 +16.4305794423473,7.25871622313818,-47.6649650657611 +16.5286620045219,7.23290344711022,-47.0157125192171 +16.4691436189693,7.2675703496341,-47.8236119241309 +16.4070927204703,7.37518617702751,-48.4337502251131 +16.3639143142754,7.35847805634907,-48.9442783899572 +16.4210018996161,7.30669070649092,-47.4181645419929 +17.0310291510505,7.09768854071323,-49.0341753748518 +17.0867074875194,7.01590785955922,-48.5233655535414 +17.1635110695591,6.96123485720102,-48.4198936857127 +17.2041253333553,6.90546751805659,-48.249500733029 +17.2316057368369,6.85625940930573,-47.3988673390628 +17.2664361875613,6.83545937346602,-45.9111809498536 +17.6930956214735,6.88831798020375,-49.8781784006275 +16.9248256270117,8.4281718760667,49.8983660467192 +16.9962846046637,8.44614029277337,49.8625554076663 +17.0281202547027,8.45217702772667,48.7063295919431 +17.0638919225967,8.51280793098965,48.6138373395939 +17.225685702422,8.57657019065224,49.0543611276456 +17.1500591466631,8.59210385733275,48.770231898623 +17.3229502660655,8.45636811946294,49.0170942456196 +17.2711361508767,8.50763835565437,48.6745804661599 +18.3905881629318,7.24005669774472,-48.3931636327122 +18.2826708900735,7.30875568325726,-46.2235127477154 +18.1754047447198,7.41237272927436,-45.8869931775348 +18.0950301602497,7.49071260190127,-45.6537320598816 +18.0801900758717,7.51624018741897,-45.5672784680314 +18.1228463003646,7.45753433779037,-45.3166187901283 +18.1230488477439,7.45254759513696,-45.9627284504766 +18.1301100980844,7.4566862945732,-46.2786831421369 +18.097147775392,7.50570036647795,-45.5770117936086 +18.0257519627657,7.57637000929256,-45.2586853280834 +17.8550570088379,7.72845072056146,-45.0513354789763 +17.5211741580013,7.60471665669568,-45.4001213835595 +17.6378551111427,7.74161016616903,-45.3944017376949 +17.4079331610889,7.35529451210495,-46.1194809850033 +17.4581522854866,7.4290711841532,-45.2230215091765 +17.2443760734328,7.31317832732946,-46.5425924653125 +17.3161200932604,7.29990916235512,-46.2803839195697 +17.1393988654639,7.49847189342151,-48.0657435387522 +17.2095484076132,7.38726611843289,-47.0272916154308 +16.9735406315401,7.76738600453947,-49.9720622712789 +17.0593503940606,7.66048116499417,-49.2365553439782 +15.4101950353009,7.38772769003815,-48.4158644876428 +15.3721483132311,7.58953390377294,-49.7880315934734 +16.9162201596736,7.85599650236038,-48.6325855351656 +16.8035300460341,8.00934985201188,-48.351675797157 +16.8628202958393,7.92239933569576,-48.270296307108 +16.7094810676499,8.0495561003648,-47.9720960625631 +16.7614492319407,8.0367963400753,-48.0390004764822 +16.7220251322505,7.89843147184383,-46.7326285913292 +16.6944730656684,7.9940695464385,-47.6814136159528 +16.7844082829272,7.61696947992499,-47.0816213373348 +16.7769023483036,7.82328097342456,-47.3103882824536 +17.0023374861636,7.41440808777731,-48.6681519415404 +16.8337658011487,7.51934172864441,-47.7936212197021 +15.3391171432839,7.77149491769744,-49.0707598115457 +15.1622639753147,7.94198076403336,-47.1025608120858 +15.2520160587625,7.8025761963028,-47.8907898694656 +14.9014178364001,8.35776905121698,-45.7534349010819 +14.8689937617111,8.25136882074496,-47.2656385558947 +14.8106155280302,8.12636391227397,-47.8048188219482 +14.7823695558038,8.09340244132631,-48.9228270650381 +14.8824833335371,7.85008527188004,-47.8949878883275 +15.0552105290224,7.76690639087619,-47.0045804758008 +15.3087858012552,7.79574927015043,-45.4363935986587 +14.9622547575587,7.75642984438513,-47.4853733781498 +15.1643303152756,7.78911743416506,-46.2628990080309 +16.1344291931295,8.18133437743495,-45.0579110339219 +16.552890896077,7.71103094944292,-45.8182057995724 +16.5570997835842,7.92371794186361,-45.0063302183383 +16.4862014697534,7.60055795140936,-48.0723418119675 +16.5396689090412,7.68133140907221,-46.4695637357428 +16.518190589661,7.67688492445405,-47.7746683277929 +16.4567412869243,7.62004409173029,-47.2986101588382 +16.4723967378883,7.64497081159179,-47.1039276746588 +16.6519241608653,7.69941176445078,-46.3738224492439 +16.7662191379423,7.55739166882174,-47.1448606511581 +16.5385570680103,7.67034526415012,-46.3349353214059 +16.78423632065,7.5741351054312,-46.7645367338686 +16.7083960098144,7.55188705822324,-46.7528607087077 +16.8255789387418,7.65316923886055,-46.7151672312459 +16.8409468850627,7.73117050450643,-46.1146419163721 +16.8416528978201,7.68897576438266,-46.5480360138828 +16.8455412695088,7.93522763009861,-46.1978788562994 +16.8431445101594,7.89169488056599,-45.458156596314 +16.814144858757,7.83384044270477,-49.8192463413114 +16.8564309689712,7.89506022875115,-48.1272738150841 +16.8817185895276,7.95286941277586,-46.7823208298542 +16.2071148619517,8.58624677379521,-49.4144846285436 +16.2865320776364,7.20939591549718,-49.6352007891197 +16.3374612622959,7.20614744854194,-45.2993173035156 +16.3190356094865,7.17182770551911,-48.0344827365834 +15.5405069010747,7.42827967817532,46.2633118560367 +15.5413391806555,7.63305857542273,47.1492609131517 +16.3254951376795,7.45239147689802,-48.6906391943121 +16.3413370988933,7.51461913775861,-46.9763308067287 +16.235185795932,7.34373758944174,-48.3639498917463 +15.4648708579314,7.76378333465652,47.1588427046468 +15.3574378678133,7.95584974063361,48.7421353606244 +15.2469387162118,8.09214173005832,49.5865133722521 +15.149326330225,8.22379339433656,48.1124442583623 +14.9825413089282,8.27457557944565,47.4116666706025 +14.9621150288227,8.46508573236147,45.1110900244451 +14.9408181402659,8.35911982914432,46.711294636588 +14.9249117574999,8.41137037980846,45.7413056289836 +15.0592123320336,8.52679613844729,46.0766188496493 +15.0095214780452,8.56327073485351,45.3590769373088 +15.0474790746,8.26947566027059,47.2990400627935 +16.1274129471426,6.68567490838114,-49.5833756814898 +16.2183045039322,6.73311269646257,-49.5582520968642 +16.2740349766206,6.83582376740521,-49.1421038766523 +16.3437315077043,6.94676331106403,-49.6380780935039 +16.451887545198,7.05874511183826,-49.6276185830694 +16.8397258646749,6.79363265315561,-49.0755120336087 +16.970597446313,6.78370568349269,-47.9648870684091 +16.9934131526999,6.90372856849632,-48.4441015445915 +14.8354326188955,8.04634629493404,48.7864571487811 +15.2362255048476,8.06615318053456,49.4417151113162 +14.8052748776346,8.23120409262705,46.5171598198606 +15.4356888785678,7.99029725393607,48.9100954497712 +15.3211850499256,8.04227041798244,49.3358440870632 +15.6707097333328,7.7294966050593,49.4324851523618 +15.57594659595,7.8630707234111,48.7864408539732 +16.6000596346858,7.89895321871187,-45.0352320098866 +16.6266166971834,7.69444923819063,-48.6764807412067 +17.0268718276766,7.05829819394642,-48.528427976828 +17.0296773953064,7.09019991991357,-46.7976429158758 +16.8993176487046,7.19637404325421,-45.040220556715 +16.968245156449,7.21284791647904,-46.215882059548 +15.4421264988888,8.04462572469714,49.9990947282358 +16.5864752013884,7.61607113653932,-49.7045864430062 +16.2528001985317,7.81839156079708,-49.2447409467594 +15.9232858561405,7.86111161857621,-49.1964012214819 +16.1709866962194,7.85238285805264,-48.1046702625187 +16.0448516574139,7.90214092490922,-47.6774608876645 +15.7807542429385,7.70894743046846,-49.1280068655293 +15.8306588013683,7.78338068395199,-48.9022631620768 +16.7382043832294,8.55431265004833,49.5092856278179 +16.7165782424078,8.56579949837251,49.628360566362 +15.7140373956552,7.58281454676201,-48.6174646646443 +16.4714280814298,8.16358254769184,-45.3819153243783 +16.492498320237,8.01829845317859,-46.468537959329 +15.9891131619493,8.79651628081086,49.8064324561805 +15.9470889676342,8.65896388903095,47.8931928064098 +15.9788183829195,8.73187375506066,48.967745702995 +15.9336554892707,8.56383014872036,46.6639650578769 +15.9197430378515,8.43741729248299,45.5596690359527 +15.9384638438856,8.06883234638937,45.6538867208303 +15.9619140920419,7.96071966657395,46.150491716412 +15.8952189196395,8.07128384512218,46.3789857400128 +15.8714525600131,8.19487888197208,45.7598542084748 +15.8496263829719,8.36490266059148,45.3497914898723 +15.8178251641236,8.57567250439952,45.6605258247933 +15.8860195773218,9.21162393695329,45.7220921430377 +15.9785534878277,9.14528235322549,46.0718409013595 +17.3578189086562,7.63268528283246,-45.6751520700476 +17.389966783006,7.64651549528988,-47.3563246137245 +17.4475012182266,7.74374942590158,-48.7954179610697 +17.5657621118877,7.81568123943169,-47.2289095900525 +17.6716349359755,7.8920810843437,-46.2462876343977 +17.6998713082904,8.01285371508893,-49.2271343358089 +16.1409656410663,8.99797300926147,45.7865829400229 +16.3084121076584,8.83765566206146,45.2230020131534 +17.6913742338247,7.62640688314179,-48.0439642850281 +17.7182372275992,7.75838153398581,-48.6029883067715 +17.6446095050627,7.46019760825684,-47.1055331187817 +18.0934918357016,7.57350997808265,46.207411176196 +18.3469321691592,7.50494677032945,46.7206746077926 +19.5165481422935,8.03215217141518,48.1871701469711 +19.562006224552,7.85454247441571,47.6324013874636 +19.5661176860369,7.74643624752779,45.8336336157711 +16.8157639337414,8.86154920831795,46.0056720367843 +16.8310596734247,8.78658022256904,47.0946239158466 +16.8132263552143,8.78031466585267,47.9066255411181 +19.6661603340369,8.16375506180215,45.562172111877 +19.6523829573662,8.22556454530723,46.5582641050465 +19.6937555227798,8.26655485912083,48.0310617123363 +19.7141724195762,8.12170882692434,48.4384629172571 +19.660272661201,8.05875970113081,48.4436013596131 +19.5369284258055,7.9880246681936,47.0862092247027 +19.3800546107199,7.97961933388218,45.528623307355 +16.7896123246467,8.69605333356749,49.7673334781751 +16.7241113319821,7.71695792555456,49.5108476064607 +16.7195343609937,7.70989227025985,48.5244360237981 +16.8117723297246,8.73101751396529,48.7374434564127 +16.7217458091899,7.75530944300232,48.0094467412447 +16.6938807595634,8.31579626268645,47.4799875914624 +16.683899876581,8.6076192379799,46.8183847499525 +16.7065996447009,8.00663323561115,47.2282406294679 +16.6828896219781,8.87873181984156,45.1018648509367 +18.3388555013618,7.60637466787975,46.2565430379024 +18.0967586019387,7.50947638323455,47.6639410584003 +18.2302950792325,7.57313556689517,47.3899710969525 +17.9533152973484,7.54000085987699,48.4494979755148 +17.7863034602478,7.55245522985267,49.3179109937257 +16.6837541154405,8.97172847892347,45.298571741889 +16.637634568775,9.13564421324685,45.6821802330237 +16.8629637749275,7.94998347857852,48.4558230798435 +16.7293086787139,7.99166312286106,46.3104615517466 +14.6490249813568,9.24957584245796,46.9941864151304 +14.8252799349219,9.190551869716,45.3641747759145 +14.3941368424593,9.27667761093568,48.7826672312613 +14.4938790794281,9.2919436479302,48.1257134948823 +14.2663446799018,9.19582960924975,49.085313439928 +14.0725294451884,8.98351149368239,48.214168616138 +14.061274478086,8.87106370148115,48.7261386567541 +14.1399826205391,9.14442824686704,48.5072208298681 +13.8684711744282,8.77750192117251,49.0466687489823 +13.895169431667,8.8233807897604,45.5204271043337 +17.4912653582095,8.05680320671844,48.4470257441281 +18.7263298516388,7.63425411929305,-45.7660292964796 +18.5664848542888,7.74463607168201,-47.2068597328954 +18.3713755977666,7.94692087219313,-49.2237141388314 +17.5876067996729,8.83538246828905,-49.5847197067971 +17.4185697303627,9.03776420717661,-49.444523389536 +14.3977162045701,7.03506888358259,-45.007128941232 +14.4086513309167,7.0443358903131,-46.6685065319621 +14.4807125658912,7.19207759615378,-48.3253006584766 +14.5191524791263,7.35033387755305,-49.3688711269733 +17.2254390377493,9.04320137355714,-49.8259804403788 +15.3978447619018,8.60840399105311,-49.9599315752544 +15.5039505832955,8.43528915970009,-48.6027165869742 +15.473121611581,8.49075608763825,-48.5439586528115 +15.5626917091539,8.22704608606738,-48.3586843373901 +15.6153219587558,8.10123474644085,-48.4574073517869 +15.5825291414488,7.91973235263837,-45.7324147745279 +15.5584363073159,7.96842635849254,-45.1285980220532 +15.6077249076409,7.94502230247198,-46.6848106808049 +15.5827254517744,8.29491976547634,-46.1445180605193 +15.5568124601014,8.2339475022374,-47.2715645962888 +15.5540574322152,8.26573107019509,-45.4755175144383 +15.6133083023433,8.20973492982433,-46.6260969066821 +15.6717359804823,8.29950532082531,-45.8028132893488 +15.7387101877789,8.1303482007321,-46.3802303694677 +15.7917828869496,7.85202179992843,-46.8514452125439 +15.699932814595,8.32929839017698,-46.3740005455894 +15.7745999871497,7.70242950153386,-48.0438208664549 +15.7843236330664,7.83257996214167,-49.3312769186698 +15.7981689708119,7.7523711207195,-47.1850630038873 +15.7285512461513,8.04253962326759,-49.5982911486489 +15.7598947887179,7.75094761542059,-49.0209202617816 +15.7175958323699,8.08652186780298,-49.7802896730929 +15.7381491865495,8.00454820838225,-49.5651041458829 +15.7398104234721,8.00899573525585,-49.8976704348935 +15.7152598886719,8.08843129458412,-49.0170237542241 +15.7115959615568,8.04743729614217,-49.3561721386937 +15.7729821588728,8.25575049937118,-49.8803733547513 +15.7758134710767,8.12579430913953,-48.893136320363 +15.7626393984873,8.17875476646296,-49.8882496602735 +15.7751304799271,8.15563438879944,-49.9394368226275 +15.77741114472,8.12643455327202,-49.7808961892672 +15.9478069904639,8.01476068678996,-49.613296159217 +16.0258937959301,7.9281959062429,-48.8831475482231 +15.9761600916907,7.98856966398124,-48.8524317798197 +16.1941005755963,7.86216535919887,-47.9028465849315 +16.0959365876352,7.86340659393172,-48.3763663742212 +16.2645513581123,7.81420538687822,-48.666536288708 +16.3169493660823,7.86071782007114,-49.7226345713304 +18.158209500281,7.95302076762849,-47.5434819468474 +18.2541105204286,8.10213068586559,-45.8725243820399 +18.4104646476496,8.06414670090293,-46.9767905496051 +18.3905850270763,7.94315792538367,-49.6038903917266 +18.4148600121005,8.00136549782401,-48.7983436710217 +18.4411201111157,8.01650824493474,-48.8548871125687 +18.3559725069738,7.5348422158334,-49.8937638651359 +18.2119946251546,7.3019117172831,-49.2422347189276 +18.1704338791333,7.27431037447152,-49.4188699704918 +18.0945577339778,7.25900914545719,-49.3938808374244 +18.020250092957,7.29227852954253,-49.3027607701704 +17.9085682144965,7.40890293324831,-49.1573206439809 +17.9619717925373,7.29670796192427,-49.0485484588282 +17.699492172717,7.3652193094054,-49.641300915298 +17.7903946075096,7.4297869959336,-48.0502256657738 +17.8480558506272,7.51481241508064,-48.1125985608883 +17.9197798632561,7.93862039341474,-49.2616101429469 +17.9004170929949,7.77533400228475,-48.2111062804708 +17.8775697591277,7.68458176000052,-48.3977251691574 +15.6207839564238,7.8077420708022,-45.5208030714198 +15.8476579349305,7.89461976931968,-45.8853405001968 +15.7220978173029,7.85487527587605,-45.8178943046755 +15.9303535189245,7.83854650122193,-46.6897002202171 +16.0083163065449,7.68936554575597,-48.0277187427927 +16.1016776379985,7.57380144991013,-48.351825905532 +16.3302372197504,7.33111458183035,-47.5618483284835 +16.1950691707479,7.46330735314569,-48.4848774015691 +16.4436361786065,7.282983780677,-46.2183620959669 +16.7599920995374,7.59248280483935,-45.6355438726554 +16.8115648156447,7.68710524642702,-46.297387321788 +16.8594111928544,7.84816855071748,-47.6573062708097 +17.0852514521186,7.99063949365715,-49.1505550042093 +17.0896979860313,7.89825031177804,-49.7828471629182 +16.9086849916238,7.82951613467713,-46.8208050929765 +16.9958142072663,7.83154639501526,-48.0543939075838 +16.8171279552713,7.9067855588551,-45.0980332397181 +19.7622302087585,8.05388385868944,-49.7147904156107 +19.9185469859327,8.11499648816964,-49.2226625638478 +20.0707545649993,8.26063050860231,-49.8558948501928 +20.6967290446139,8.068600558675,-47.301006789405 +20.5790342495286,8.09171325012825,-48.9446362780933 +20.8938399825234,8.02914921294641,-46.9917846507906 +21.0134396035767,8.05736048367946,-47.7749247545232 +21.2709443350375,8.01529201277968,-48.1289365480548 +21.230928986198,8.04535247366506,-48.7087839620238 +21.1477266761636,8.04075190486653,-47.5942603678348 +21.0225814843994,8.78161118064589,-48.9561127403244 +20.8067405997348,8.60718350148481,-47.4053311407209 +20.6927628498668,8.48167264741371,-46.1919746151877 +21.3195531328597,8.12911487769571,-49.0085374808202 +15.8188082512971,7.68128880362572,-45.2662872490586 +15.313392574594,7.82816904657674,-46.2946318384276 +15.4646252271592,7.96047507200503,-47.2009237567087 +15.5638453312809,8.06768621771264,-47.2831258884877 +15.6573761273821,8.17054111858033,-48.4143376152652 +20.0021399804144,8.46994467862158,-46.1116365931096 +15.7340666402012,8.12358401920008,-48.5958111699942 +15.9366567551436,8.0774518635543,-46.8695558578144 +16.1229375731138,7.78619183416391,-47.125238834978 +16.2335662400437,7.62049513087322,-46.9621909363744 +16.3729700113033,7.6071152741704,-47.2516029845825 +15.8132916090929,8.05881262206798,-47.4412970251366 +16.0248737432238,7.91490106630797,-47.0327242317147 +17.0239732197152,8.00252670451668,-45.6199701003686 +19.8649322466625,8.54138568572856,-47.881853766125 +19.9702387313509,8.53683374516978,-47.5127728269572 +18.7302844269778,8.45753650390382,-46.4600566060091 +18.0574197504401,8.55131877831878,49.363108661371 +18.1220831551566,8.58081920491483,49.4531397095625 +18.1680074258604,8.60393531910829,49.5305854228693 +18.2166022017115,8.64042906176328,49.7566153258148 +18.5618030066959,7.92369787601502,49.1229204261874 +18.5507253167863,7.92804854663944,47.2292753680128 +18.5178519382388,8.11537059697028,45.7668237825932 +18.4701504741418,8.2650835018242,45.5302179086045 +18.3597655939478,8.72682835433335,45.8919467360264 +18.4239711994539,8.52567143808295,45.5046403190279 +18.278373980948,8.94501297741666,47.4238613450329 +18.1985313580031,8.9864822273001,49.1205468118405 +16.826355134242,8.11950155626665,-45.0938325439718 +16.8329743227297,8.14705871805176,-45.265231187127 +16.8473570539742,8.14114538511336,-45.1177889304971 +16.9028921609492,8.28663703970443,-45.3294806004422 +16.9396048842043,8.05419934836279,-45.077595776551 +16.8901388346815,7.89898339825955,-45.5675163595244 +16.8755115659329,7.84661295241516,-46.1047379766792 +16.8427594113137,7.83104332872231,-45.8858155328255 +16.6644102457988,7.97932146095864,-45.7842161507905 +16.7809492513209,7.84592643927028,-45.4929012980968 +16.8300945780675,7.8338572474658,-46.6200423372678 +16.4001746026534,9.30767323838788,48.41619267362 +16.2771050336934,9.09702108600264,45.8742598460428 +15.9347917721433,8.70752095927844,48.045811919223 +15.9800434438829,8.72709318490178,46.0011221509431 +16.4555632478758,8.11279840772269,-47.169013165364 +16.5996127117567,8.07495445841007,-46.1031825216392 +16.2868844561443,7.93484035585446,-45.8811446341357 +16.3418119020806,8.02306088765721,-46.6945449854535 +15.8539043336724,8.71032233499475,48.9020394296748 +15.7413985915219,8.72768262386225,49.8999675730934 +14.4942464803419,8.11165439807351,49.820141798149 +14.4654376962015,7.98854315096411,48.6969135160076 +14.4122642911783,8.00163170626345,49.4409964887711 +14.3893445336183,7.95930221635075,48.9828303555721 +14.3451979466369,7.89003525997164,48.8149507599514 +16.234161690803,8.56657089492356,49.8406713030764 +16.0877060767698,8.64882891910438,49.5607319298927 +16.1792031348647,8.64187558051399,49.5931487408658 +15.0802236386827,8.79979116331499,49.025159357112 +15.0759808076107,8.75916818069146,48.0278006005629 +14.2044386310062,7.9174274709005,49.818653900023 +14.2589723475362,7.97732835754354,49.8990412499032 +14.380205310458,8.72843305478484,49.1160706933875 +14.447977677159,8.82244302104906,47.6973322156136 +15.0721485531118,8.77852817413883,47.0033518183578 +14.5497462546511,8.86297065028076,46.1065811714538 +15.0799561889878,8.75663244758819,47.7056396037914 +15.1157997107902,8.83267632030908,46.4574271714048 +15.1118508225993,8.82457735924539,46.2906433235113 +15.0963467593204,8.93812666581424,45.0582208889629 +15.0514782620691,9.18621006971328,45.2638507357707 +15.0199524802681,9.35385961494456,45.4283057397602 +14.982392017119,9.52929811524193,45.2072776402021 +14.9588918683214,9.60883184725713,46.3452804072825 +14.9697941503023,9.64118554760166,47.5095934341851 +15.0382149685673,9.62548264576942,48.9282006578845 +15.3231492476551,9.34119714612051,49.2522004220336 +15.3728860910149,9.34004509846049,47.5005037234124 +15.3621341035927,9.31793296183464,45.4122214698346 +15.0806122519983,8.88266097867909,45.8934222417784 +15.0502861287654,8.92767866598194,45.7219386552097 +15.0863352014011,8.86148061354437,45.6281502601032 +14.9805562883067,8.63954611883279,45.6711807383772 +15.1013668136924,8.25164160826346,47.3700848603912 +15.0434787125615,8.43677091670933,46.7986407888288 +15.1294879395573,8.15206190150229,47.1622660812644 +15.1338846115592,8.10913619869104,45.9058794758222 +15.1379344773127,8.12443105029611,46.2882063372315 +15.1051528234741,8.09997341632771,47.2983250033878 +15.1218488045629,8.0667441224341,46.829074050409 +15.0806740125681,8.03333628101893,48.7464995188768 +15.1041243951127,8.05427380654462,46.9737947557176 +15.0224191812299,7.93002261725835,49.411667049698 +14.713470335174,9.40859976738363,45.4950441695633 +14.6563337375135,9.21893138360941,49.3041721365516 +14.663306793679,9.34397399354577,46.7974457054634 +14.9648067357051,7.88496071262799,49.3716816455607 +14.8205487977544,7.90513282347628,45.0224957124466 +14.8999092519299,7.85081319162542,47.7815976791118 +14.8566773806354,7.85128306270167,46.2016045312369 +14.7833812623049,7.95337545658248,46.7255519368609 +14.8380750459457,7.89555268914363,47.4499839920059 +14.9141773577629,7.90143960880432,47.2621513442704 +15.0059111364451,7.95827700592033,47.9352391700148 +15.0897683769541,8.03544080363152,47.6646855160512 +14.7497158308163,8.04483088936795,46.3082524366907 +14.7264662524397,8.0659743238093,45.201311474973 +15.1609029477875,8.15655888857952,47.7602514919868 +15.2410033345371,8.27425828402077,48.4334917811515 +15.4728687520958,8.36069354129851,49.9571765823424 +15.6366521659876,8.26909175067233,48.789558798525 +15.7842405823471,8.2092457302591,49.245786198292 +15.345405736788,8.36147845102139,48.5510190185715 +16.0200956056583,8.00441058276874,49.0377649333661 +15.9208434028908,8.11174281892742,49.6312807986542 +16.1244903435663,7.97949465834306,49.0194182975289 +16.2012919669417,7.92702779692682,48.5794444263582 +16.1738547584432,7.93138028992843,48.1058687999969 +16.1816393984168,7.77702931915633,47.2392819955919 +16.197084395192,7.89203551051863,48.8720938987175 +16.1952330080449,7.75089351920321,45.9349163980289 +13.1947852763636,8.9228486209658,49.4691335008768 +13.278543722824,8.824809869474,48.2323194554746 +13.3087983030947,8.73693758495824,47.9556452010477 +13.3077705701434,8.69015995004966,47.2712630205384 +13.345607928391,8.64446775260076,49.0079315533808 +13.2867563795104,8.68361919096883,47.7423169965602 +13.4333480286216,8.61916877244375,49.5520371136187 +13.8678502988445,8.28894476177271,49.2365531463284 +13.9377639576661,8.40954454291592,48.8211142230979 +16.1318933099978,7.44326789804785,45.0733768411359 +16.0170746544735,7.16240792479945,47.5799311899989 +16.0839838391988,7.35912393906795,47.2117774470262 +15.8726674133276,7.00910437470333,48.5642577815577 +15.954618341937,7.01859764919919,48.2620176026873 +15.7376333918734,7.15075503955034,47.3377335941679 +15.7874326949962,7.0491667375131,47.3811647506606 +15.6740795755248,7.33141196213809,47.26204020511 +15.6720806081511,7.38036490946882,47.4353608055858 +15.71528470554,7.284877999579,47.3496582914156 +15.7496740008699,7.17166090445096,47.2778883472699 +15.7618757869914,7.08364356436481,46.7097029398893 +15.6947847740694,7.26597204382153,47.0907164410924 +13.7991261862919,6.93407457008467,-47.6317094198103 +15.7407869180453,7.06581369339593,46.2502085653267 +15.6832648148873,7.33687098399485,46.32459642834 +15.6481741042115,7.46824478246159,45.8233733001719 +15.6294105989689,7.5842765023151,45.697392179944 +15.5758913205494,7.64202656935085,46.0726733525771 +15.5305316882053,7.59898566158783,46.7973764064548 +15.5142727880542,7.56079302522824,46.9357909406331 +15.7072927341984,7.19784124283756,46.7060582868056 +15.509291835455,7.5473429677178,46.8329257945786 +15.4861831016887,7.53097693892973,46.9392135830244 +15.5071158231258,7.50358945745162,45.7132373148396 +15.3871278696255,7.58863717944044,46.7320819707259 +15.305961467376,7.61218062227371,45.2854452592677 +15.1949871957049,7.6353853313361,45.0527504696209 +14.3463378453914,6.99327649194699,-48.530225396299 +14.4397541779931,7.09467150362576,-47.3444566383964 +15.4378940284483,7.5619138558303,47.3933885053089 +14.8026760216761,7.40977399687025,-45.14904603527 +15.1217765020761,7.8234882833947,-45.4542718687505 +15.1206447852975,7.75036523317553,-46.2731795346345 +15.0964797149701,7.70522419248961,-46.5819362964899 +15.0502290567739,7.70232417455239,-46.6637189994989 +15.0340245153296,7.77774135292427,-47.1705557916999 +15.0598535159525,7.96152936085025,-47.8571416096762 +15.0322022974788,8.02532719832696,-48.6113967789842 +14.4010250930778,6.68855900397712,-47.623865375099 +14.3289255126612,6.63252646234689,-46.7218013379679 +14.2384354441295,6.6051563756327,-45.3917501857555 +15.0930339004665,7.68644868808657,45.6099617374863 +14.8883170522736,7.77330116054596,48.3437704321724 +14.8015823246054,7.69464593775075,48.6585783112585 +14.6970025388711,7.583648882297,48.8696396516077 +14.9839573051136,7.77899874954182,47.5453775702403 +14.5912586595444,7.52825253114703,49.0993660092913 +14.5216898852338,7.39706444856145,47.3139614971471 +14.3668681919371,7.17372275546439,45.2799498486303 +14.4857492424601,7.33381323194628,46.0147245884433 +14.4175381234009,7.24336904187409,45.2795646917827 +14.0947217629441,7.11470354787514,47.9310085812538 +14.0120030680883,7.09456514643313,49.0682512069539 +14.1700951090734,7.12522073886475,46.8328120308861 +14.2312341146075,7.18980699320591,46.0747071094702 +15.1578245249505,8.36031148302409,46.9042374064506 +15.1602631023485,8.58320631109862,47.4494616730874 +13.6421115605921,6.90803887310659,-46.1561425632653 +13.6795832091964,6.85804007212133,-47.5117982855984 +13.6862475687774,6.77758196506665,-48.4272057973417 +13.8095909904601,6.6713871917837,-48.782839838995 +13.7412556535706,6.67524600615859,-48.7009692630353 +13.8907532071958,6.60676011212195,-47.6408351385193 +14.0213801708581,6.60027756258853,-47.6905110560644 +14.110123726026,6.65111052140849,-47.2988102472559 +14.0626630432144,6.62577256255871,-47.4142404649043 +13.9785220037324,6.62062228272032,-47.6757858565303 +14.1963396839532,6.69331169410596,-47.2965786596848 +14.2941917447001,6.83664430164036,-47.8689378972193 +14.1993192969347,7.64061464112392,-46.930412078703 +14.0072519374091,7.67904364511599,-49.7037743563556 +14.1141361889576,7.60392156750879,-48.2392707406877 +13.8969326147663,7.63677986305174,-49.9363838359667 +13.1804995646711,7.4412310257541,-49.5533829349311 +13.1709734891137,7.40923109039145,-48.4388207263115 +13.9192035849027,7.81838560917618,49.9604616190715 +14.0060781609344,7.9087194236458,47.0790630106416 +14.0802239043089,7.95291353849162,45.988878540191 +14.1645551017746,7.83446368056697,45.361558050531 +13.1778099818447,7.31804672582204,-46.6673268976272 +13.2331943428026,7.38150950381032,-45.6523844243509 +13.1709638781206,7.3369790641939,-47.623624540014 +13.3067441684909,7.75968336215613,-47.2704960806327 +13.3436354236504,7.96989867371261,-46.7768153030485 +15.1629279341489,8.75910504033368,48.4577737342423 +15.1680904443389,8.62308721502098,49.1568246690169 +15.1706093312904,8.45313699170249,49.6210135124149 +13.2785848564859,7.57397838367212,-46.4633523097042 +15.165488625102,8.74162752700771,47.8432400665116 +15.1859296448527,7.69503723487184,45.8234344024389 +15.1901433694825,7.54560909774959,45.6564062935647 +15.1923298335688,7.4336413447065,49.1711118985839 +15.1879749459705,7.60251760387532,46.3649700493125 +15.1942832374967,7.35241222393812,48.668168528 +15.1751028549318,8.13125852025724,49.1085430548728 +15.1729192482584,8.26144310794411,45.9331181069594 +13.3958357021797,8.18935733634417,-46.3015108427757 +13.3834354350892,8.07516008304939,-46.1759696025617 +13.3777264511445,8.02099797464425,-47.3065376621591 +13.385441457507,8.16807392521334,-46.0453206383112 +13.4053599617119,8.05615191764166,-47.5521735079596 +13.3239457326093,7.95785675823227,-47.4148448835358 +13.2892129705039,7.92718979701223,-48.5165513670943 +14.7827660380451,8.24062878811324,45.2301510659475 +14.9016762026275,8.21476063871464,46.1701893589914 +14.9746097992145,8.16871532741805,46.1754518644166 +15.0083317825095,8.20717192552814,46.2475870363515 +15.0193316195209,8.19062596383561,47.5755257360574 +15.0380859296494,8.20266111131713,49.2868903065395 +13.2940713702407,7.68480551348488,-47.1860777663485 +13.2102149050008,8.04534724285791,-45.5527427109403 +13.2179073351315,7.84136977218453,-45.7913795838319 +13.1989269072677,7.94449210147225,-45.411380508783 +13.2178059852521,7.68102505857067,-46.087875000408 +13.2212172279104,7.41080757651611,-46.1198258875975 +13.2027118438837,7.26247748612689,-45.3646643868784 +13.6055214892668,7.42962897460738,-45.6652381963734 +13.6629401873698,7.58476817188537,-48.9105503430626 +14.0731921750088,7.80683344532965,-48.932486297585 +15.1962752633659,7.2662031617503,48.0271434213434 +15.1983047678549,7.14963013024164,46.8053462598647 +15.2002046020928,7.1158362520612,45.0714664733558 +15.2042217878046,7.41513794312856,45.2635859880704 +15.2062751128261,7.64759708530483,46.974307228378 +15.2107156993768,7.85300974245612,49.8654994206694 +15.2151182278746,7.67385546507666,49.9271810710275 +15.2084121872776,7.81143318754958,48.6669092420477 +15.2171114963092,7.49515807266408,49.9436961067696 +14.8381658418597,8.62030982356696,49.682857668969 +14.7722356075711,8.70284740668521,49.0823122392322 +14.7181761938778,8.69086796588009,48.5413274177878 +14.6248306984168,8.68012317339661,48.2564203479501 +14.5070816552752,8.62990541400023,47.7937085741207 +14.4301298747109,8.54389431206329,46.6695889636825 +14.3462584950957,8.37920690114909,45.980556740102 +14.370189761158,8.48183134500647,46.7129704587605 +14.2634696022514,8.3338237696647,47.075823726437 +14.3288316529635,8.31412279315438,45.8090728260535 +14.2810066911618,8.34238474249526,46.8602847363426 +14.122995008813,7.73334599861868,-47.83819620132 +15.2362881276865,7.84335554954838,47.3341149901496 +15.240615508019,7.94166679118688,46.5840188919939 +15.234232782994,7.72743286636207,49.0709309754003 +15.2427971645347,7.91724133018076,46.3516224959025 +15.2384241558864,7.8943199412771,46.582407519666 +15.2470335530122,7.73480392117006,46.2664059346112 +15.2448446932619,7.87997427156689,46.6000725057785 +15.2489965684066,7.56440221616995,45.5892788165063 +15.0199601469033,7.93845521237497,-49.4498450659034 +15.2619605475152,7.38140231153216,45.7346853490324 +15.2638828007181,7.38346231944268,46.17346671409 +15.2657445184029,7.34144234007303,46.5576299210142 +15.267554125801,7.2937789494608,47.8677896132633 +15.2692553570855,7.24511523414196,48.1202730096829 +15.2708655299728,7.21235893708218,48.0332364779714 +15.2726318397065,7.23995776856484,48.6932552568358 +15.6467647806321,7.6478539109525,-45.2717316927803 +15.5054743024042,7.53809732989945,-45.5759097200927 +15.9027067579579,7.97722457351502,-46.656561864937 +15.7788989327919,7.77079432510935,-45.0777511342957 +15.8899378694916,8.01754434690716,-46.1539832969497 +15.9713803690782,7.99702142025828,-45.6536342896009 +15.8988184992681,8.08540163792296,-45.5729968962129 +14.2390833754288,8.3357794953311,48.3806245541811 +14.151014305342,8.19830510624347,48.8146036739923 +14.1298504788181,7.96512205784368,47.1894814206449 +14.121583887318,7.92256796617428,45.9966019413042 +14.1986332872546,8.25910519137683,48.2874754284036 +14.1416896398116,8.08713403699341,47.7832017712395 +14.1643458721045,7.9789915522847,45.2034020928725 +14.1488484329577,8.00634509003537,46.1942951062308 +14.118524326289,7.86197976484707,46.2720524855733 +14.0959622569943,7.8383286497407,47.4970929495168 +14.0459625220511,7.8882872059961,48.0512635378349 +15.2907867213658,7.95171051084304,49.8565068548097 +15.2941416508493,8.17212699643202,49.3821312806967 +13.9792283052005,8.0164504193084,48.9579681368834 +13.9469957564243,8.21821527865307,49.1771706250874 +13.9849693683009,8.4047393069147,48.0069326765588 +13.9900695934658,8.54952342684605,47.5711285265239 +15.292410576365,8.0462206397753,49.2872223389197 +13.9985940258316,8.75787596114063,46.0254659707815 +13.9920245962607,8.6911640175163,46.0126211313552 +14.0130251870922,8.81864681034478,45.8843191562551 +14.0040266990661,8.82140639690344,46.3376013195991 +14.1094836618156,8.76747095313794,46.294665634379 +14.044631263732,8.81554128996806,45.7968494074835 +14.1845433421,8.67603396824687,46.0034781579809 +15.3147765572536,7.55067959345806,48.8791649208061 +15.3151172458536,7.44643552670883,46.5073428287004 +15.3148668415163,7.48297302338999,48.1170251944661 +15.3151850883479,7.42970399842706,45.311435897968 +15.3149355082582,7.51724928780012,46.5110329337576 +15.3151388939436,7.49537908848281,47.0814837293814 +15.3145664513229,7.63814810951811,46.6434939344894 +15.3137488090506,7.78148954509182,45.6948752440247 +15.3141812610089,7.69579156959167,45.8359414323463 +15.3133758764219,7.8362339380114,45.2314247186965 +15.3093434045913,7.79114248836853,45.1508349503292 +14.0132559925131,8.18517362842503,46.5335214260897 +13.9575078968253,8.34841368366497,48.2372641428322 +13.9570626829356,8.46060626962185,48.3913347988019 +13.9733349269804,8.59917264167506,48.4720271408807 +14.0253385755169,8.75133929025308,49.0835264129061 +14.1057145122495,8.63265916399149,47.9251251746074 +14.0962863352796,8.51103503233142,47.0340211946317 +13.9831333535014,8.67895195498494,48.6390730376689 +14.0029183207072,8.26289921961152,46.0127222641295 +14.0420690856092,8.35531081495083,46.2892242630865 +14.0620622589994,8.74224833281573,49.2669085310931 +15.2983988481417,7.31954800127354,45.1163844490441 +15.2950092404796,7.30223743458886,46.1527419784323 +15.2911441519222,7.33056645508512,48.8295169732658 +15.2818146473914,7.44333101309776,48.8299076176545 +15.2763628178021,7.55324427610065,49.6766386284366 +15.2700176871834,7.65589999239159,49.6335010361515 +16.6623119325678,7.45968519919573,-45.6805085699036 +16.7284902648431,7.480109853638,-47.1568047878323 +16.0160341278737,8.6470588675368,49.2683177653224 +16.0880958769375,8.59286737940619,48.3678892320819 +16.1718844920372,8.49612640416917,48.1983114916362 +16.26316866921,8.38329407449349,47.5775837769136 +16.3479793050726,8.27113256166517,46.8901074108812 +16.8544356292206,7.45597646489022,-48.1048918480542 +17.1720552825901,7.62021565649102,-49.9711251427809 +17.1370887398406,7.53171244717532,-49.8324729759638 +16.2682665621239,7.8296495758379,-49.112673924526 +16.3424579686604,8.06980054978731,-47.6544919276022 +16.224311410712,7.5973954537423,-49.218845491927 +16.9981562285553,7.48899566486445,-48.8870517713921 +16.4182823478398,8.21888678607481,47.0744495807 +16.4914393066805,8.25032842831772,47.5635402302295 +16.5230477669221,8.30811632887975,47.8561010330928 +16.4993947012411,8.33374090595469,47.1825813265524 +16.3519229839247,8.37160071770002,46.419537061041 +16.4293402698099,8.44574008980266,47.6583944443527 +16.3139629959167,8.3356814095465,45.9248735784735 +16.396616878303,8.2247989602172,47.2540268164857 +15.6532932901932,8.16774557958614,46.0193124809321 +16.0268812368672,7.4521606471628,-49.1334004699268 +15.8351729711389,7.55362971855052,-48.4530804914249 +16.1479927295281,7.50772180519961,-48.2238714233872 +15.6602685082617,7.54571424699513,-48.9692567901562 +14.901545990254,7.62282868384571,45.079139943081 +14.9929018354023,7.63661061456146,47.1116062116711 +15.1341941366173,7.74475096119781,49.1209446564625 +15.069618616808,7.67728744802864,49.1323052415044 +16.4510670617417,6.90021923309986,47.9027978934699 +16.4414060836493,6.87495482888812,48.721619560892 +16.349871514853,7.09333673889876,49.1716531235283 +16.3870931894007,6.99669154675352,45.5949540179013 +16.406591566858,6.93167072323434,49.2236125762683 +16.3185997102205,7.3000155652037,49.8676469646048 +16.2674960360541,7.49716860582702,47.8954846181407 +16.2967107448807,7.38608820223778,49.4283279043708 +16.2434977674544,7.58857879308499,48.1204513646354 +16.1459396260204,7.51355155208779,47.6686129408856 +16.0822164796332,7.45037862249145,47.9013475776886 +16.2139876884201,7.57905720811569,47.9507613432891 +16.2251274774049,7.60009038137622,47.4324395621672 +15.2467321133016,7.8655342919939,47.7327597420521 +15.4661259328754,8.20269147685966,45.0369541562916 +15.3721243853424,8.04422898952934,46.7804455668478 +15.4657645944537,8.1448656314497,45.2498604234743 +15.285551174043,8.06285853673182,47.6610395599071 +15.0766731496966,7.98744284971739,48.2844358548593 +14.8190462399438,7.89556290181522,48.2969266372546 +14.5832438461853,7.81765358179458,48.0221542806878 +14.3335891318088,7.85866231816687,47.1999158983147 +14.1521641166295,7.91772773628401,47.2948363518888 +14.0020098969873,7.9908949961272,47.9077655221194 +13.888240694151,7.95986351208157,48.4829144623258 +13.4687752394684,7.87811069777354,49.9697830525764 +13.7208515145216,7.95681724286558,49.6034558424461 +15.636401236699,7.81633577439532,49.7226027579453 +15.7303631768035,7.77547085933146,49.6058272814605 +15.879547541791,7.75368286128455,48.944096000437 +15.884133469919,7.7524254723634,49.6466709250411 +15.8028968927764,7.74466023004205,49.2219336537142 +15.6421449802658,7.85854973569107,49.8126540482608 +15.5022632397675,7.95843031260589,48.2021003643614 +15.3667921223148,8.08782462474239,47.8332749330912 +15.6416386037732,7.84263664780816,49.5806027131115 +15.2566834336214,8.21983424946693,47.2762496954782 +15.1847303646659,8.26471450460637,45.6509528051407 +15.5916865590509,7.80740012184813,48.7468956979823 +13.0985259142479,7.51127538213923,49.1002079369795 +13.0122478905687,7.5737235918299,49.4274605516492 +12.9400732018811,7.63761374895735,49.6231563005535 +12.9468288295629,7.98962817169014,48.7056806733211 +13.0311012586536,8.12181190464034,48.4811679930552 +12.9425568605218,7.71811064842003,49.1243061039716 +13.3771687647217,8.09760487961685,47.8438330209195 +13.5851242013752,8.13839832924147,48.2336518338582 +13.8050272293707,8.14966896417358,48.1347458776483 +13.9919344707783,8.16396002830349,47.3790471547719 +12.9238575005743,7.84379247897805,49.0552718052149 +13.182353135069,8.1426173540197,48.5856166254577 +14.1740215648688,8.20609236934025,46.5197374142771 +14.9649241582134,8.52433635695605,45.1945732448349 +15.1081584137463,8.5092960781778,48.9985009768367 +15.0423996165401,8.5750997761927,47.0221884352774 +13.6359441687776,8.00346833024552,45.082485810272 +13.5861008642222,7.7757386203796,47.6869933396554 +13.4707859491911,7.48969790624115,49.5328602778359 +13.6371619110077,7.20079088165135,-49.2197056827266 +13.598844718532,7.16565661336939,-48.5801049920484 +13.5874223001899,7.30952389173264,-49.0736919147306 +13.5862751195734,7.37531848617777,-48.4066045638676 +13.6055549895959,7.49143845896,-48.5406552782377 +13.6197416922053,7.60947208919063,-48.262304254291 +13.6203544532559,7.63053780116692,-47.1803518465391 +13.6028032480727,7.6296257655491,-45.0152814302256 +13.2503509305593,7.36634610857237,-45.3489457015033 +13.1779717954158,7.37718118645831,-45.7961691399992 +13.0802892635158,7.46389173241198,-45.6148091221221 +12.9886370605064,7.57743636535438,-45.5095612202119 +12.92611410509,7.56865452733896,-46.1978010601335 +13.3294471769376,7.33866648334205,-45.0532428657312 +12.9116688178091,7.56794371834033,-48.866845444286 +15.8054696836623,8.24047844453651,-48.9461087377991 +15.8382445019513,8.23797803956506,-47.5795655739437 +15.8908059904756,8.10998399181849,-48.5492356414539 +15.9777450238281,8.08927769795006,-48.8906000553707 +16.0497850135255,8.0641410053361,-48.046592822382 +16.3750024523517,8.18781137293341,-47.1704119773895 +16.5786670350152,8.62363962468931,-48.2414758242064 +16.6664953333865,8.82563426579947,-47.2735047862098 +16.7754598602592,9.00504035094414,-48.2785927218638 +16.8202110329682,9.10955513674922,-49.8861804639483 +16.5245421720708,8.14642222959227,-49.2444467069651 +16.4554551581728,8.27549722323902,-48.1405340077974 +16.4246215800549,8.28797413681492,-46.9475984597856 +16.2631987031273,8.26886575599416,-45.040277522165 +16.3839276564548,8.37140335839236,-46.2680073618239 +16.1350992037639,8.22616508068764,-45.5688297494156 +16.0517932034561,8.18643829280801,-45.4693143608593 +15.9058622911345,8.16331986615047,-45.7090568456189 +15.6665104984113,8.23416978075656,-46.7342844404366 +15.755052366126,8.20118154252061,-45.9913443141272 +15.642179760192,8.14153758054701,-47.163575845625 +15.547670319469,8.1356953103135,-48.6138208872862 +15.5868220921058,8.15235815322585,-48.0784996499311 +15.5089380239034,8.23096891992556,-48.4789792635737 +15.4026958638879,8.45912739397121,-48.7771699780681 +15.4697335348076,8.33944142506401,-48.1764615383297 +15.3454349779373,8.62348141090843,-48.9217474482445 +15.173098079471,8.74343022380546,-49.4373361412648 +15.2580376704873,8.73278135658623,-48.8830199648951 +15.0952164708949,8.65236544528425,-49.900190334697 +14.9143370514389,8.52928248447306,-49.2515892175161 +14.855314854021,8.49937435681665,-47.8383317023948 +14.8205216742156,8.51437283444858,-47.4643498878118 +14.8505388745074,8.48802005433987,-46.2879127861105 +15.1973795115513,7.52619734190877,-45.8840599485887 +15.1993773745483,7.53561180899168,-45.7764983692056 +14.8464196545782,8.25478298785383,-47.7323842755182 +15.6450155208186,7.36968084080053,-49.553327565775 +15.7498873569995,7.26399711101758,-47.3759415669315 +15.8473641613211,7.22924891928329,-45.9023172212743 +16.3428986839728,7.68249608340871,-45.6657548780466 +16.0739513614925,7.3223052860609,-45.1813748714355 +16.5540701849038,7.81464774750724,-46.653689131466 +16.6978942448722,7.95752025258733,-47.2313605779275 +16.7469229572751,8.08399140406314,-45.6674628014633 +16.7570247022676,7.95558647133371,-46.9156763809196 +16.7339224647731,8.12089325051205,-45.0716371130906 +16.9393867472374,8.44306178096806,-45.0818444989652 +17.0726234148651,8.50424223341907,-45.5097663851294 +17.4726021497997,8.65561643970732,-46.9645446223346 +17.3503469905469,8.59593151790885,-46.8679589008538 +17.2174384644722,8.52554410692785,-46.360104733922 +17.5645149123203,8.73669318478069,-46.8572296979353 +17.5923679733707,8.87471240436113,-45.5690844623055 +17.6331348952222,9.05257118234162,-45.3474025990885 +17.7015435107615,8.84620501250184,-45.8291681812108 +17.6659891070387,9.03450877475233,-45.3207728271268 +17.9458486336704,8.11511778722661,-47.2675271103145 +17.8858502771347,8.44206657653928,-46.5506014720366 +18.0105272161444,7.87268309353336,-48.4467319224625 +18.1000437875833,7.66520219290722,-48.747779483852 +17.7992938967585,8.70687550835246,-45.6657394300398 +18.179005268358,7.63755892206015,-48.1108112445768 +18.2897701637665,7.67838643265032,-48.5140001124779 +18.581927053551,7.93445090352351,-49.9375526496442 +18.4494609716295,7.75950692774452,-49.0627752357374 +18.6631063947802,8.03831592375293,-47.6770876967879 +12.8430405772608,6.53840829482284,-46.8336709138344 +12.77600195136,6.82474988949267,-49.8740494171918 +12.8093021137962,6.75569308828248,-48.0063145618556 +16.7535504369018,7.93577486250801,47.0359254011802 +16.93475514335,7.98320286251571,46.9493740389565 +17.1481612884575,8.03215331823928,48.54529893892 +17.3716657119069,7.9638357014097,49.257018728908 +17.5768316208455,8.04491061334272,49.8451876791435 +12.5239921271995,7.35691833645219,-49.3170088924419 +17.9086940823871,8.09794254948483,48.8210979945474 +17.9878554205743,8.12154816544477,48.1852047685638 +18.1365497117551,8.04934458928066,49.2711685445124 +19.6752274940079,8.32586740908398,49.9324073758661 +19.7311639319958,8.24156651397203,49.4652074107431 +19.7482416402713,8.22181286911391,48.1115928893762 +19.638928096394,8.20160262436843,48.421276182117 +19.6250137094186,8.16342789618709,48.7717325858692 +19.6672228043293,8.04354606001337,48.0681958422999 +19.7127913584932,8.2057999469883,47.7725519067745 +19.6870027577577,7.88315668328969,45.4058021546508 +20.1622023270013,8.23985231882444,46.9900430013765 +20.1521740965062,8.14891854100279,48.4831053000307 +20.1309826841427,8.14304571054081,48.8878576367408 +20.0882667785386,8.31512896938196,49.7274349250286 +19.995871118877,8.38205968815303,49.4078056504083 +19.9976042108664,8.3412953815885,48.7837296956566 +20.3158269103709,8.39860399942241,49.4652927042453 +20.475710031001,8.42532555958715,49.549744231434 +20.7163594516746,8.38660791377877,49.2224780175008 +20.9029536194778,8.48964735557138,48.594668983913 +21.0168205486835,8.41246286499787,47.5510334588687 +20.1129978681634,8.31339496326665,49.1658922001538 +14.8101126127061,8.30259609182603,-48.0635262174459 +21.0536477766049,8.5033287927929,45.9255785754837 +21.0157760657733,8.50832754896458,46.1611554879912 +20.9835407915623,8.39532531482943,45.2675611006334 +21.0074422015488,8.28356727898542,45.7827581393861 +21.0063047728878,8.28787413479224,45.8495408884905 +20.9299709555343,8.27704633103183,45.2297038276092 +21.3014873615456,8.67817484256536,45.3008784795667 +21.3580038440654,8.59437339052875,48.8387082020953 +21.3246904615612,8.62451600731565,47.3457680558055 +21.1915269972772,8.51205739818337,49.8044834514591 +21.2250251311588,8.54602938021954,48.9976740347694 +21.244570899519,8.55210690671785,46.825764073858 +20.6020640430164,9.17838004694472,45.6554866051926 +20.5521644971082,9.1581122500159,47.4516676883228 +14.7477979415489,8.0857615278272,-48.9129752809392 +14.6876029288537,8.06940150146288,-49.8015907697119 +14.2439615408465,8.21453821008394,-49.736872423517 +14.3101435870811,8.11834471616745,-49.4223141081724 +14.1769649401193,8.35046829584007,-49.8054073266725 +20.4538193645587,9.13104668483711,48.8093053304906 +20.0469575214374,7.90984088338409,47.5923256338195 +20.1185165246796,7.79390519676831,49.6182717771899 +20.0402022235781,8.00723211926777,46.5256404888181 +20.0823956672384,8.10484570831448,45.9291612475579 +20.0385839528174,8.19838607274976,46.0198012139772 +19.9616510918776,8.30256179010678,46.2207709942724 +19.9957189099819,8.49347241359766,46.2443425463289 +19.9937127614552,8.6702843568844,47.6468977824189 +20.0018623452669,8.83387771048659,49.3288636569837 +19.718127058042,9.53198292671186,48.0606399047479 +19.7843861598304,9.70517169943522,49.4312079619447 +19.6530575536226,9.26951080544424,47.4113295205913 +19.5770746463967,9.04590038894204,47.7560493290727 +19.4611253535119,8.87063606049701,48.857828398813 +12.8389211561328,7.91908365244584,-47.95733931199 +12.7581703428791,7.92631393043226,-46.9375746999358 +12.9194851410041,8.00076373220553,-49.3911861320136 +19.2816441529888,9.10794404975079,48.8717133756044 +19.2316730159401,8.82465390871028,47.3988258762695 +19.2166149819686,8.60990681168126,46.1882060920217 +19.2031335931196,8.46467779492764,45.4730381090581 +12.6765488578617,7.89703758086102,-46.1496381055728 +12.5116055286831,7.9853075652018,-48.1232630830448 +12.5942162422195,7.95936190986835,-47.4087901945571 +18.9878445113168,8.93943212415434,45.414154076348 +18.9404017305657,8.90522764139693,46.8063498379249 +18.8499172846886,8.83141153129848,49.5330809479417 +14.5200008591956,7.18586930743219,46.7146460329958 +14.5057459875141,7.14965633908884,47.4943721823228 +14.4915508443467,7.06453838878546,47.7762271634147 +14.4774312545912,6.88560694549947,47.1505608005002 +14.4633355439828,6.63454326134582,45.8240033434556 +19.8893674544746,9.39357783332411,49.0554314599412 +19.9681699654525,9.24743917682929,49.5163563557905 +19.8132006684676,9.06217693578155,49.0584476080865 +19.9079172159335,9.16159915115127,49.4559962570061 +19.729648801366,8.89918114372101,47.8840329695337 +19.6120824992033,8.69441342671955,47.424636904566 +19.4925044295374,8.62478849960908,46.5996123370101 +19.3306165911472,8.53930932838261,46.2265664328779 +14.3229110050039,7.21000537633964,45.2508992914903 +14.309063871967,7.21865213744815,46.1404117798285 +19.1951837530607,8.34567674804184,45.0419362130278 +14.3214864931764,7.25492711401315,46.5487885751446 +14.3333178418472,7.23219002705381,45.7159633086442 +14.3446049105556,7.15322881490745,45.6475952703549 +14.3553809507857,7.08072488810203,45.8615859054219 +19.0737493504779,8.51289654923208,45.9927133592003 +14.3656606896556,6.95838776850817,45.9465288168694 +18.9780440695881,8.59875459103297,46.4921605474527 +14.3755069206057,6.96673888582086,45.7164969768672 +18.8786307406718,8.89017618340372,48.0806777686691 +14.3849566395454,7.04586614908798,45.7993750399481 +14.3939774981628,7.15056405056797,46.5575623606418 +14.4026050424847,7.31033975491896,47.2608737689722 +14.4108594005186,7.45291740038326,47.5791730084397 +14.4187909420868,7.47949251138957,47.7321155708593 +14.4264163785764,7.5262074064064,46.9898413391901 +14.433700510854,7.51005403475117,46.5649194105065 +14.4539607228391,7.61842176727334,45.318349290259 +14.4661341607471,7.84151831441788,46.1105427229473 +14.4719332681633,7.89832125889008,46.9644962046759 +14.4775299037238,7.95401688241419,48.3931941734439 +14.4828925065506,7.98843788918005,48.9809581535925 +14.4880484937526,7.99492786315326,48.3482342785473 +14.4407439097315,7.53670234777186,45.6833352449968 +14.4601803390283,7.76310541916822,45.7211530469225 +18.3900813064882,9.26594254982881,46.750957960928 +18.3551723991313,8.96412124862785,49.2176160671524 +18.3514280787247,8.99085356846067,49.3002577615789 +12.6446619304498,7.95231455371373,45.3714058110031 +12.6708032096402,7.92298717617945,46.6621819505515 +18.2737438632901,8.8857063539057,49.6358128344725 +12.2061688531538,7.84589778652666,45.1106258609384 +12.2438655624037,7.79006759903863,46.4868664477478 +12.2618614021244,7.80535866134016,47.4909185878375 +12.2792759661549,7.83981203774608,48.873219833605 +12.2253055842626,7.81014730518401,46.0462578150789 +17.5070694615065,8.801903549265,49.5998673233496 +17.3804359151303,8.92267687863876,49.9820254756665 +17.8553966849385,8.75761370372096,49.9535689579492 +17.8827656632068,8.81638101947457,49.6056632907059 +17.9522770318772,8.82137882338975,49.9751457378992 +18.169622473642,9.08240030392725,49.9563735123239 +18.8389520469892,9.27505265086544,49.518914219416 +18.9156957547256,9.32720687873996,49.2620115457197 +18.9707230037396,9.5599436686416,48.6265335813708 +19.1190356313511,9.71631724319658,49.4546009743565 +19.0378424667086,9.6674903295744,48.6678422287777 +19.216759176402,9.74931942795174,49.3692756140276 +18.6840911099952,9.28075271520204,49.6907015227603 +17.5276414669077,8.70113644021436,49.6297295074696 +17.5356261225983,8.76098320239912,49.2642637147107 +17.8128303601195,8.63089970968641,49.909516439389 +17.9320256824174,8.76766063781079,48.956545660871 +12.2961962246791,7.77502668674682,48.8541290838861 +12.3126195473469,7.80552029572561,49.6504556893789 +12.4764754226642,7.7950454741943,49.9804876591238 +12.4878902606523,7.75487613435611,49.969619742836 +18.0430787157665,8.83642215479917,48.4445745104001 +12.4989291325517,7.6227944516019,49.4378295423585 +11.9733581645605,7.48013730654471,47.4653202989911 +11.9938512897312,7.52187906252098,48.1049927386332 +11.9529961884671,7.51174479222467,47.8301859696982 +11.9327607870829,7.57626910268441,47.8904712503569 +11.9125981674983,7.59003828559287,48.592800165221 +11.892696069493,7.61741792531395,49.6834745075233 +11.8139634187003,7.4053363957398,49.6286079536001 +11.794599950817,7.40931102293187,49.7350142014549 +11.7752849474698,7.36136799554313,49.7046070341124 +11.7560787366716,7.28816987171014,48.9922157248175 +11.7370583179356,7.26235700640689,49.3466918133424 +18.1206916383104,8.94282216903818,48.4707031996834 +18.1639548533039,9.06494854827449,47.6204810223767 +18.3196127951608,9.15714696784409,47.5851725696416 +18.1901424224493,9.13240482250661,47.1411195945401 +18.6142980055842,9.1971468579762,45.9268545934698 +18.5167402567609,9.19070280371093,47.0041385791208 +18.6313232835098,9.22605908891418,46.0269747628921 +18.6658386407392,9.2589063423712,47.7636348323925 +18.6560229284294,9.29784375700146,49.2455665188804 +11.7181568429318,7.24415796123748,49.7365449251549 +11.6807546188102,7.13878156882517,47.383858470131 +11.66222543614,7.12225476164951,46.2806787049529 +11.6437585027917,7.20972822291241,45.6422558111468 +11.6993984608105,7.18147585172405,48.8737371309806 +11.6254059573001,7.28316090570874,45.4339449057683 +11.5891515057601,7.5808176427558,47.5476602272462 +11.607210584474,7.43189273810192,46.0316761988996 +11.6496556032983,7.25114587604674,49.9520356197034 +17.9805234386991,8.60726848477074,49.8061928499592 +17.9098635595853,8.98387826196521,48.3657531928943 +11.6643196312345,7.27461988732301,49.2782256676298 +11.6926150319335,7.46920125781012,49.4711711976884 +11.6786168761643,7.37634330024607,48.8733307868497 +18.0743090147823,8.93227324547733,46.7943689643456 +17.9688456850192,8.98280352250211,45.9387838626264 +17.928246859304,8.97425009191882,47.1612654819817 +18.3323521945441,8.65404462999669,48.0217271105914 +18.1553142779269,8.83412914674798,46.4160256836492 +18.2244583914927,8.78531549950807,46.941163972575 +18.4088163070787,8.58675408490968,48.1882260346583 +18.453274327434,8.55428176545027,47.8146377389305 +18.4739390668833,8.66614170708766,46.6976079629428 +18.5188435720603,8.7342350189801,46.5363972961325 +18.5470586015953,8.86922049281903,45.8060657717074 +18.687256551502,9.0842646468379,46.3480797813785 +18.768913616732,9.24954716826048,46.4507628039988 +18.8132171028697,9.43393912043776,47.7785871487433 +18.6113342061613,8.98144197451033,46.2650555984673 +18.8182064576133,9.54316630728751,48.8923915337052 +18.9018664164493,9.73860156492893,49.6146455243828 +11.6188219359632,6.39096539054656,-45.5661397587524 +11.6278905216303,6.40880156240313,-46.7097071201676 +11.6003099064923,6.60756391350081,-46.9768083681109 +11.6096251946405,6.56995780387277,-46.5898041062185 +11.6005053185558,6.85571998219476,-47.9261308378367 +11.5908686359494,6.73131749179481,-47.2446983892393 +11.6197074186676,6.9274089992668,-48.7134357111066 +11.6101166796217,6.87282304466437,-47.9274426533965 +11.6292736107676,6.90778513492891,-49.8264605451428 +19.8119510943175,6.83934979457433,-47.0318071130059 +19.6805691150015,7.13752647306153,-48.5256702772109 +11.8577563556959,6.9559647555293,-48.9060386772874 +11.8493022061864,6.93432143427261,-48.6772630193925 +11.8407542420042,6.96437041528448,-49.4788028688406 +17.1878762783866,8.11910782397831,-49.4233499087011 +17.0482488857952,7.95565008518748,-48.1462261860436 +17.0731688609375,7.9613534099095,-48.3072904983216 +17.1314637816996,8.04590119440907,-48.5064497578767 +17.0922551930071,8.02406155702085,-48.0367551749993 +17.1229619612608,7.99650109651715,-47.9650536519416 +17.3070388274821,8.07602779468572,-47.0773574985676 +17.1646377398402,8.00936715150902,-47.6983196220385 +17.605924951595,8.36316053664515,-46.549336016147 +17.4909355716812,8.15769533116678,-46.4292118937894 +17.7697600772447,8.52761720132813,-46.4726516056127 +18.1010249039236,9.01346211216525,-47.7448230442358 +17.9373943597637,8.75784075504924,-45.9071188837032 +18.2801966910766,9.08066673864879,-49.3628975113891 +18.4336358710161,9.24419198310117,-49.5277222175636 +18.6031440690505,9.25820083402992,-48.001194624712 +18.8088382875368,9.20093627629459,-49.0709174525981 +19.0321617896017,9.21834444229148,-49.6011050783232 +19.4986281733598,9.0605751079456,-49.2512256273675 +19.3532595781352,8.76203664865476,-45.5567982598833 +19.4197504518267,8.81441677627743,-46.6004189339278 +19.2715187051284,8.8974419472879,-45.2199659957126 +19.1727044161253,9.12282640952795,-47.9202491784886 +19.4335400394125,8.85847835147763,-46.6761027510823 +19.1090558549612,9.21207662426682,-49.1752311042355 +19.2405924417112,9.09276057564667,-46.3385536068668 +11.8022147832692,7.47997969906477,-48.6518850580851 +11.8129167757634,7.27297695921705,-47.790595391914 +11.8236011708748,7.09717135575353,-47.7489517095019 +11.8343271503958,6.98000179311286,-47.8378953854753 +11.8450517113,6.96203990963096,-49.2927253875991 +11.9203133447019,7.27164859566546,-49.7861781670325 +11.9310996665616,7.21224172424205,-49.5949954008029 +12.0179597822366,7.03022344297514,-48.9991327221805 +12.0288377916779,6.96657398266842,-47.8215459893479 +12.0506402084927,6.84936066811642,-45.9681590045797 +12.0615404096053,6.82565726329382,-46.032624228729 +12.0724585432891,6.84883057201808,-46.6998019649423 +12.0834938442034,6.99277182899336,-48.8726047057979 +12.0397635514166,6.87949711931319,-46.3080240818443 +12.2061775014516,7.95345871947107,-49.5795984300598 +12.2174337979869,7.94468408837564,-48.94440298507 +12.2287077937276,7.86128798477201,-47.0852308099506 +12.2400101582091,7.83076167259684,-45.5207894221291 +17.5587587110517,7.0985652940946,48.083636304075 +17.4490433897512,7.45107992424162,46.8955425365744 +17.380063025901,7.57100867719799,46.8047425147916 +17.3475399993126,7.65431059860937,46.2374249954564 +12.379071793689,6.71629929926677,-45.3469995615565 +12.3912332125091,6.75154238833011,-46.1660394068212 +12.4159865031559,6.83352895484059,-47.1204125408656 +12.4283356942374,6.92924644224792,-47.0065470848763 +12.4035860338,6.75090361028921,-47.0320752886579 +17.3141216848059,7.83988624583212,45.9022102357424 +17.3465210366007,8.10826487157903,45.3242078466733 +17.3177762083246,8.01517895299428,45.373209188665 +17.3358800836033,8.29272304421398,45.5070529673445 +17.3771320909145,8.45216199396984,46.5960805238818 +17.3563194862787,8.41118064417832,45.8959808369363 +17.3976658016255,8.50935887964196,47.2087111156937 +11.2688196284426,7.00132218197846,-45.4596058687435 +17.8826338612135,8.83832990671687,49.9008442420146 +11.9554116217865,7.89639301519286,-47.376351701392 +11.9593722403408,7.9677893702568,-45.481688437626 +11.9517421064591,7.81397432986563,-49.5159850540695 +11.9173063202406,7.16874248574158,-49.9287917966567 +10.5129586824527,7.10185912842712,49.7698810246285 +10.2465116854665,6.94013741935472,46.9594103205609 +10.1091511743049,6.92350361319088,46.5553261054595 +10.3960938427341,7.00424826552587,47.9570625083801 +9.88097783928955,7.03725372970541,47.9659148520218 +9.82397322842143,7.02955069185849,48.6820893355992 +9.97675032291593,6.9800193914623,46.8069425286861 +11.8718780941307,7.13356300996451,-48.82434185709 +11.8656405981273,7.03011611185613,-47.6768911120175 +11.858852195309,6.93779709733054,-47.7735846610248 +11.8516320538267,6.85805753567009,-47.3817997978961 +11.8439608349051,6.90417409388653,-47.0882609199409 +11.8357571484615,6.97310597597634,-47.3832939596072 +11.8169936300904,7.18339170736486,-46.6484499967625 +11.8062994402371,7.27080525939608,-45.4981587475217 +11.8266408434038,7.06311801602121,-47.3829885684389 +11.5073891778076,7.93820847222161,-47.8691283289878 +11.4010881347279,7.8977673177907,-45.6500738754042 +11.5583363715415,7.82215826272722,-48.8784233677879 +15.4169400261404,8.31641477886063,-49.580868039842 +15.4137019031898,8.43745897753724,-49.2665556042987 +15.4259898354949,8.34266557360151,-49.0970508827859 +15.5268621964554,8.31170269847223,-48.0773991143179 +15.6017543561542,8.28536454927292,-47.1285766532912 +15.6729861300734,8.12213111349554,-46.3086647476241 +15.7556555131962,8.82725614846139,-45.8343042879843 +15.7113022983478,8.51683039386467,-46.5198576374081 +15.7302916196367,8.68603638239298,-46.4975876208439 +15.6905443664581,8.31622625004135,-47.2359662385644 +15.6430038317173,8.23119016129038,-47.5369482341525 +10.3938767734006,7.45128331411889,48.2605128652077 +10.3939315277227,7.78255609723893,46.9556182467996 +10.4058294222924,7.61146658074149,47.0776884053462 +10.3590718277078,7.93423748499709,47.2034783438325 +10.3994395619924,7.87250742802121,47.9098279616272 +10.4030500949752,8.00982850767553,47.7204809028015 +10.405408170826,7.99236761270657,47.5478776523874 +10.3189290286428,7.98878034484691,46.9630481934019 +10.2963478629855,8.07621803319918,45.197454509551 +10.3473787833451,8.00218650948909,47.442564649164 +10.2773678374091,8.08014230473535,47.1581269015842 +15.5368037599873,8.13468344828379,-46.0703568322088 +15.6236715383527,8.15138449850437,-48.391715257906 +15.5844170559068,8.12795405895291,-46.3316119020103 +15.520934185085,8.11887057923057,-48.1604433658111 +15.5496016171679,8.12670862455242,-49.068122542369 +15.5919676346729,8.19781421795075,-49.8241391441324 +12.4048981161531,6.91284062355519,49.2609067546873 +12.3544891470001,6.76471578247834,48.5142312072429 +12.2814021206853,6.78607044907646,48.9554332045699 +12.2157612414426,6.8214001094418,49.0707988288256 +12.1689346714605,6.79804192533926,48.7540239365506 +12.12806106807,6.70206276502183,47.6275012453967 +12.1056957522723,6.4773366962303,45.6743084338162 +17.0656280791261,8.72606494216087,-45.2537517493314 +16.8986144764123,8.20440739726236,-49.4502943913171 +16.6591304126557,8.23383613115121,-49.0761281841031 +16.4275732502809,8.81895897176948,-48.4320848195332 +16.5765863284868,8.43135387350461,-49.3201294975312 +16.3617898291334,8.96480373871534,-47.6783330705411 +16.4938085784157,8.64862163060261,-49.0797836579123 +16.2842260809327,9.12795481745972,-46.4632850228656 +11.7136932733018,7.31189380891671,46.3575115592042 +11.5990160478247,7.23174976869848,48.4928513307981 +11.6745529264924,7.27576157362216,47.8844444392911 +11.5326991078661,7.2869052514785,47.8195055054332 +11.5513175167793,7.26209473816896,47.9680535494456 +12.5902313032353,7.58453653685624,45.23799343998 +12.9487330237239,7.77442536711446,46.7942696838171 +13.2641079783704,7.67045911624643,48.7661099623264 +13.0791202803585,7.80809278389989,47.7000881864001 +13.3151505419495,7.58931275164004,49.1128687281044 +12.8042325561748,7.69910061913752,46.5003568323522 +13.1966486018444,7.78409484385862,48.5340898061831 +16.2243990650045,9.26408386754373,-45.488822708988 +13.4152547240376,7.50027367833736,48.7743118379969 +13.4597528224969,7.56323751631851,48.3239781862407 +13.5004546363544,7.62690722013203,47.4124030567525 +13.5685278427015,7.65280494164838,47.6370152775013 +13.5805055777608,7.73704714295557,49.5950987833445 +13.7371077587241,8.00710231250642,49.7442768452663 +18.0696142518856,9.2491199852484,-46.5822141381743 +18.0950871421633,9.21591572054029,-45.6423351888752 +18.042419774284,9.00759206272001,-46.4301750987214 +18.0247209258916,8.82386515085991,-46.9547427387708 +11.5138402162161,7.25642760629752,46.6700359914856 +18.0644116025267,8.27174865143034,-45.689763424621 +18.0123587110873,8.24238437705774,-46.0899295921505 +17.9338281181901,8.59981602015187,-46.0834538609691 +17.9825893051997,8.37024214987371,-46.3357974014189 +18.0041148048823,9.04820932172684,-46.9777362568854 +17.9642067888598,8.83696384889571,-47.0142184188496 +13.6968732594366,8.31911229480762,48.926200881903 +17.9756536121198,7.88953475806891,-45.2648875123952 +17.7861469854378,7.60384509850453,-46.3288136251294 +17.6385207985197,7.47413104129464,-47.2234310578019 +17.6313290221131,7.58454685836797,-49.015145527147 +17.6965014978518,7.57706459476108,-46.4517564029578 +10.6914528695115,7.32776391095174,48.9429637270803 +10.7444541783494,7.20159297316554,49.969101548513 +10.7713030258288,7.168452955982,48.2676416617784 +10.793933209993,7.14641642200492,46.5974108942194 +10.8022735059297,7.10902947176844,45.7545803153403 +10.8114895231491,7.08252245353785,46.0625012541201 +10.8498277928191,7.00490167825827,45.4193018914361 +17.7258118345544,7.72803198494463,-49.7847295558789 +10.7369935129694,7.38119373681327,46.568045043827 +11.1968737932696,6.92101260069546,46.1226135052513 +11.1928359636406,6.92039210834963,49.0510239760239 +11.0643866132737,6.60445068814757,49.5939822698531 +11.0277311550279,6.57896465823435,48.8030048061507 +10.9734817581014,6.61384539871976,48.7871235868036 +10.8828304380321,6.73479524943327,49.0054157799887 +10.8283825762151,6.8376207256272,49.5858742277964 +10.860454704835,6.9226756715022,49.847451884087 +10.8449585344369,6.89714054976106,49.9976264679433 +10.9790901244379,7.0449758756727,49.8212855849507 +10.9967981295704,7.28228493244577,49.2642986918534 +10.9990213108142,7.17091403852848,49.6026808372945 +11.1034546592306,7.51919319312304,49.5175400607974 +11.0312966055337,7.39974114000715,49.4761396868532 +11.1579288007603,7.61291905047241,49.6114684265824 +19.9768444738514,8.53626081101187,-49.4138574522669 +19.6138522770599,9.33823785508602,-49.9680514406231 +13.3111895910636,7.08551540690147,-45.8529486173435 +13.3089630098874,7.07859870806234,-45.8282145518262 +13.2726499154321,7.09482615830765,-45.048092214809 +10.5184413443224,7.07163105162648,-46.0809565941814 +10.4897943605071,7.0301707158837,-47.2578655849986 +10.4631350143633,6.91693269705942,-47.8279750346666 +10.4461638358759,6.81075878274834,-48.2667932620583 +10.4422054314738,6.6887490759777,-47.930688612046 +10.4793221825884,6.54582355505524,-47.7192652107493 +10.5439757894624,6.3936700676912,-45.3022632184482 +18.8908412700325,8.98735087373193,45.4356902024033 +19.0403160867503,9.0212691797528,46.2217648833352 +18.5842217423705,8.76185488065253,49.515140233001 +19.2008311371863,9.11422986552159,47.429728756067 +19.2841666122322,9.16117718191624,48.2014874418823 +10.8848914523292,6.44889705684526,-46.0635173085162 +10.9395282232675,6.58250377699611,-46.8665375433103 +10.9683258717986,6.67793243971153,-48.6171958662886 +10.9941532825965,6.82929500770365,-46.9908243456343 +10.9902908682869,6.7870321712094,-47.3499902193526 +10.994181020155,6.85558106914849,-46.6473723426761 +10.9809902462501,6.83884243204402,-48.0963358084266 +10.9484853763445,6.79284913584971,-49.8615402759115 +21.0552464310794,7.26989056191404,-45.1837093635778 +21.0830458551477,7.56944408848263,-46.2798293662127 +21.0955997210217,7.9701124861572,-46.0611052711338 +21.0774772828245,8.22388313126726,-46.5165351833282 +21.0322122941261,8.29836015578367,-46.464553328611 +20.9287407739496,8.17683252591816,-46.615281787864 +13.5960232922,7.23726376032278,-45.2413712371003 +13.5977843165319,7.11678356771496,-45.0567986394834 +13.5201169014706,7.36640887770845,-45.3052605010067 +13.4525229376111,7.55556227083231,-45.4191433906112 +13.368173876708,7.73923080036502,-45.8489058963675 +13.325539800842,7.8684952821522,-45.8536862737341 +13.2433189732488,7.90351341557132,-45.4260796017344 +10.8862895835027,6.09003979275277,-49.1241491184098 +10.8423511989683,6.00231200847248,-49.1228304314748 +10.5685163326524,5.83876072951182,-48.9395843998745 +10.4880260660363,5.94742299692577,-48.3937834752383 +10.4184585855466,6.07714457551399,-48.22648580422 +10.3619381324937,6.09560841135251,-48.2323058798134 +14.3272926583099,7.17252854081823,-45.9889747765089 +14.3916389661771,7.13492282045499,-45.2407601607017 +14.2598500268584,7.24338311605472,-45.6014151354801 +13.9960124684696,7.6367125873566,-45.0398205015711 +13.9005282652737,7.92639071372242,-47.7237764826308 +13.9387918525593,7.81332944597807,-46.4667461630962 +13.8625067862446,8.10498301683756,-48.6158408009634 +13.8846035435396,8.05531089648,-48.2676878324829 +13.7710055561817,8.18853977316637,-47.9272212879601 +13.8480149392616,8.13814122909795,-48.4983129884416 +9.89650937655692,6.03190385320256,-49.5746635937313 +9.76561402682202,6.06786885299728,-48.7593004747397 +9.81983538173743,6.05081071281775,-49.0936555213293 +9.59802569915434,6.1994981751737,-46.5073065519431 +9.68894010971723,6.14897900759075,-45.4764341893405 +9.57166481453293,6.32183811918243,-48.9493115738004 +13.7477785567471,8.15773437986383,-47.616403216224 +13.7546377221748,8.19202346648514,-46.1440466875062 +9.77083463857558,6.59795022115777,-49.7969561430799 +9.70700835222495,6.59033712914125,-49.5728418497922 +14.8674395006821,7.9808960896506,-46.5311751754542 +14.8803861812445,7.86289974525759,-45.7072444587021 +9.8427243348541,6.74743648669327,-49.9105565841795 +9.99816071425821,6.72337200501536,-49.5514364903529 +10.0752385478858,6.68804731387345,-49.9485478891927 +10.1097900907223,6.66989161031609,-49.8312982314374 +10.1422884883137,6.66625555286219,-49.6422364614698 +10.1409006951201,6.6989060164789,-49.5474613320322 +10.1073000339878,6.76048110504155,-49.4597977055343 +10.0885509337724,6.83969064926799,-48.9570872133453 +10.0981462071938,6.95722411255771,-49.502614227371 +10.0581271184249,7.06904242119222,-49.9346899794188 +9.92333586370072,6.7205579072744,-49.3325992815999 +10.0368731889373,6.84293466501698,-49.3122428317293 +10.0353774192082,6.70549813250145,-49.489736899474 +10.0321049752666,6.50409469744566,-49.456913497103 +10.0202823952208,6.31388728280134,-47.803835618295 +10.0228355553582,6.28098464720358,-49.8458138945495 +9.98426055103067,6.38448009761627,-46.9808094907917 +9.94233646258663,6.55092294920822,-47.5285801381621 +9.88657256846303,6.67072111768033,-47.2109521303119 +9.84418393913421,6.73532195451298,-46.8806396345 +9.79903887051525,6.97213349740358,-48.7271989898574 +13.9894771328381,7.85227227160192,-49.8091663621721 +13.8962285626717,7.90696198062095,-49.3556170594094 +13.8166191083554,7.94153023551393,-48.5308779252893 +13.9540130528868,7.86144173180932,-49.6204372224917 +13.7458220990043,7.94015750804745,-48.4784368515683 +13.6964941536995,7.92293077375882,-48.3147131507856 +13.6743295813692,7.83556785994466,-49.100114690122 +13.7176462950211,7.86175703580899,-49.3847459644361 +14.1982257202792,7.46541957258468,-49.501900903069 +9.79771079310039,7.1058699824245,-47.1071827149166 +9.78634123018319,7.11005525529818,-47.4847181564731 +14.3283381135504,7.3737938552592,-48.7533224549692 +14.5474948252886,7.33398092718789,-47.6444794348374 +14.6918996643383,7.29746300009142,-47.3050222002185 +14.4547761337786,7.31737456474975,-47.8319737500931 +14.8851907673519,7.2897772148795,-49.0229550344698 +15.5818552532463,7.84801920751851,-49.9626586765102 +14.7708759470841,7.31341252615324,-47.5905055505511 +14.6339335073996,7.27120407362912,-47.1256298451169 +15.6591538023326,7.830025191307,-49.9987590840062 +15.623770693771,7.80860682358597,-49.508500339153 +15.6281705400552,7.96239502647247,-48.1555819174822 +15.6748543632518,7.97005132665048,-48.1484918878375 +15.7331817417822,8.03163736424706,-47.9046305611444 +15.807051670399,7.96446234083721,-49.1323325216894 +15.8939270304324,7.95087478119797,-49.4555628185902 +16.0882544248228,7.74822752621116,-49.6116795620015 +16.129728430686,7.6020145875173,-47.882327711564 +16.1947981756494,7.4903342738203,-46.1763329443801 +16.541873539965,7.35743390832031,-45.4577724601855 +16.6940098211587,7.43562402138853,-48.701377559093 +16.6107032364798,7.41292652101377,-46.9095841661793 +16.7370743783933,7.4916875909524,-49.8576684936276 +16.6979510792123,7.44594829253418,-49.7663296365434 +10.6920332791599,6.87513122576411,45.3401280805757 +10.5997307797203,6.85166924475053,46.6121753072127 +10.4692334422005,6.8286923031758,47.5885180165645 +10.361272666504,6.86899203016192,49.0170919272323 +15.5646838851745,7.61604964609124,-49.6435069798013 +15.5690368900237,7.6241248755702,-46.853283255914 +9.99112566325734,6.89651645113586,47.1860868708108 +9.98691656918826,6.8644729939369,46.0839001632497 +10.0184970388655,6.85411011322507,45.5956528710582 +9.97162686677326,6.9643700442657,45.3818026970653 +9.87910263914446,6.97704494306317,45.1160479739151 +9.80023817918783,6.93813355271617,47.8078837506508 +9.843023117492,7.00758701630438,48.8492813262338 +9.89422566470211,7.07741031188808,49.9769511156295 +9.99518027317869,7.21036445601219,47.6395381985355 +10.0105594063437,6.85842520829454,47.2630945157855 +10.2813174409225,7.21041835714166,49.5657601233199 +10.315974617199,6.90412713959317,49.9729632761936 +10.4933061934449,6.68844922436207,49.1092426858944 +10.4899845262976,6.7076089760352,49.058123297563 +15.5583873474812,7.54670647014076,-45.1424933806885 +15.3326239164212,7.54459364405027,-45.9689786821216 +15.2562062896426,7.51092302390735,-46.5176428692378 +15.1209713398749,7.4491381619782,-45.6638156097954 +15.2036563152278,7.5345522120392,-47.0998229165048 +10.273855834606,6.60725104837026,47.6728871730988 +10.2199042670798,6.62708001010706,45.2538048309503 +10.2963515668028,7.0455262845065,45.1092205699405 +10.2868347274386,7.04696122983488,46.319914024583 +10.2759114781728,7.10707076349607,46.8083228572954 +10.2646315762627,7.12755295810474,48.3553003928679 +15.4626628029212,6.5747526191773,-46.4379776225695 +15.3803998679836,6.35817245177134,-47.6137169002687 +15.4185489840907,6.50430070671862,-47.417046353509 +15.305929847822,6.40595233537715,-46.8691845938791 +15.3243759700668,6.40891990038936,-47.1179868016948 +15.3072014211764,6.43045092248753,-48.2052078384264 +15.3075940164661,6.44090843923469,-47.9469083380246 +15.3224182608385,6.40642322822394,-46.0213388177064 +15.3123178758337,6.41353510496401,-47.822912387254 +15.273051697436,10.5278049024716,45.1049936262802 +15.2461532767894,10.3427952350305,45.305035256379 +15.2249650079632,10.0309428508346,45.9116468546214 +15.2433581004298,9.63285461890445,45.3015283857212 +15.183463218666,8.11041273116113,45.1936482774751 +15.0893944257185,7.56184391351078,48.2417464827944 +14.4156111742532,6.35421377316894,49.4532391432405 +14.4842725401479,6.07653104163128,48.6766048811156 +14.4432374162501,6.22506705538116,49.6390830805034 +14.5770976871965,5.87952154542738,45.3799917842534 +14.5364046246607,5.95419706287457,46.8867038112893 +14.4204844507956,9.841322090465,45.7182570502533 +14.3095603739362,9.54981648506327,48.164600829553 +14.3614591303377,9.67025597673351,46.890073981461 +13.5972755460886,9.69714032266545,46.9161935267504 +13.5963957124221,9.5983582073541,47.2011225918252 +13.6151259978781,9.56217285454464,46.4394057534747 +13.3301057792983,10.6496383099489,47.0003071568332 +13.3458238675777,10.6443606536814,47.7334808339115 +13.4369711181215,10.7288401077602,48.1508903204691 +13.5024617668321,10.8296903619618,48.2924952770129 +13.6441583433379,11.1206182374318,47.354883382677 +13.6817292755839,11.2947545550377,47.3547829421733 +13.7315894708094,11.3900653933044,46.7365180899399 +13.8406184696387,11.4381175089121,46.0786105440788 +13.8348174975723,11.3087675656467,48.1018127677371 +13.8664194423417,11.2602035390288,47.9859071658947 +13.8902144294514,11.2250610197746,47.9276480178973 +13.9338470712082,11.1061981602135,47.5135228602954 +13.8166573064446,11.496049438837,45.2824272547022 +14.0201095478744,10.9642754821954,47.239847340179 +14.0343172907223,10.9054770955029,47.5124073750088 +14.0207686795785,10.8099074774062,46.9819854778186 +13.5646330799454,10.9379019369885,47.5839055305619 +13.9518413276424,10.6960426760235,45.4216879842371 +14.00570338129,10.7134971247557,46.5344313556325 +13.5537389271067,11.6922718395143,48.0801764009139 +13.341895116391,11.7982627333382,48.2336459309942 +13.2466843599119,11.6719474633561,47.1155545883161 +13.1733497517324,11.4403256432455,45.3228535086006 +13.4360253988304,11.8525037452307,48.6348180856946 +13.625048994749,11.3353324798298,47.4015506167814 +12.7977366956583,11.8729516266834,45.1902298024456 +12.6471691786415,11.2984912558669,49.2022249046589 diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/clusters/test/BearingClusterGroupTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/clusters/test/BearingClusterGroupTest.java new file mode 100644 index 00000000000..25108f84544 --- /dev/null +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/clusters/test/BearingClusterGroupTest.java @@ -0,0 +1,29 @@ +package com.sap.sailing.polars.clusters.test; + +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sailing.polars.mining.BearingClusterGroup; +import com.sap.sse.datamining.data.Cluster; + +public class BearingClusterGroupTest { + + @Test + public void testBearingClusterGroupConstruction() { + BearingClusterGroup group = new BearingClusterGroup(-180, 180, 5); + for (int i = -179; i < 180; i++) { + Cluster cluster = group.getClusterFor(new DegreeBearingImpl(i)); + assertThat(cluster, notNullValue()); + } + + Cluster cluster1 = group.getClusterFor(new DegreeBearingImpl(-179)); + Cluster cluster2 = group.getClusterFor(new DegreeBearingImpl(-178)); + + assertTrue(cluster1.equals(cluster2)); + } +} diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/mining/test/PolarDataMinerTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/mining/test/PolarDataMinerTest.java index f42c269a5c4..c8acd31d067 100644 --- a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/mining/test/PolarDataMinerTest.java +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/mining/test/PolarDataMinerTest.java @@ -9,14 +9,18 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.TimeoutException; +import org.junit.Ignore; import org.junit.Test; import com.sap.sailing.domain.base.BoatClass; @@ -26,12 +30,15 @@ import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.SpeedWithConfidence; import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.PolarSheetGenerationSettings; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.impl.KnotSpeedImpl; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; +import com.sap.sailing.domain.common.impl.PolarSheetGenerationSettingsImpl; +import com.sap.sailing.domain.common.impl.WindSpeedSteppingWithMaxDistance; import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.MarkPassing; @@ -51,11 +58,27 @@ public class PolarDataMinerTest { private static final int MILLISECONDS_OVER_WHICH_TO_AVERAGE_SPEED = 30; private static final double EPSILON = 1E-4; + + public static PolarSheetGenerationSettings createTestPolarSettings() { + List levelList = new ArrayList(); + for (double levelValue = 0.5; levelValue < 35; levelValue = levelValue + 0.5) { + levelList.add(levelValue); + } + double[] levels = new double[levelList.size()]; + int i=0; + for (Double level : levelList) { + levels[i++] = level; + } + WindSpeedSteppingWithMaxDistance windStepping = new WindSpeedSteppingWithMaxDistance(levels, 0.5); + return new PolarSheetGenerationSettingsImpl(50, 0.1, 20, 20, 0.1, true, true, 2, 0.05, true, windStepping, + false, 3); + } + @Ignore @Test public void testGrouping() throws InterruptedException, TimeoutException, NoSuchMethodException, NotEnoughDataHasBeenAddedException { - PolarDataMiner miner = new PolarDataMiner(); + PolarDataMiner miner = new PolarDataMiner(createTestPolarSettings()); BoatClass mockedBoatClass = mock(BoatClass.class); @@ -150,22 +173,26 @@ public class PolarDataMinerTest { track.add(fix); } } - - RaceDefinition mockedRaceDefinition = createMockedRaceDefinition(); - when(mockedRaceDefinition.getBoatClass()).thenReturn(mockedBoatClass); - when(trackedRace.getRace()).thenReturn(mockedRaceDefinition); - - for (Competitor competitor : fixesPerCompetitor.keySet()) { - MarkPassing markpassing = createMockedStartMarkPassing(); - when(trackedRace.getMarkPassing(eq(competitor), any(Waypoint.class))).thenReturn(markpassing); - } - Calendar cal = Calendar.getInstance(); cal.set(2014, 4, 3, 12, 00); TimePoint startOfRace = new MillisecondsTimePoint(cal.getTime()); cal.set(2014, 4, 3, 15, 00); TimePoint endOfRace = new MillisecondsTimePoint(cal.getTime()); + Waypoint finishWaypoint = mock(Waypoint.class); + createWayPoint(fixesPerCompetitor, trackedRace, endOfRace, finishWaypoint); + Waypoint startWaypoint = mock(Waypoint.class); + createWayPoint(fixesPerCompetitor, trackedRace, startOfRace, startWaypoint); + RaceDefinition mockedRaceDefinition = createMockedRaceDefinition(startWaypoint, finishWaypoint); + when(mockedRaceDefinition.getBoatClass()).thenReturn(mockedBoatClass); + when(trackedRace.getRace()).thenReturn(mockedRaceDefinition); + + for (Competitor competitor : fixesPerCompetitor.keySet()) { + MarkPassing markpassing = createMockedStartMarkPassing(); + when(trackedRace.getMarkPassing(eq(competitor), eq(startWaypoint))).thenReturn(markpassing); + } + + when(trackedRace.getStartOfRace()).thenReturn(startOfRace); when(trackedRace.getEndOfRace()).thenReturn(endOfRace); @@ -183,6 +210,21 @@ public class PolarDataMinerTest { return trackedRace; } + private void createWayPoint(Map> fixesPerCompetitor, TrackedRace trackedRace, + TimePoint endOfRace, Waypoint waypoint) { + List markPassings = new ArrayList(); + Iterator competitorIterator = fixesPerCompetitor.keySet().iterator(); + while (competitorIterator.hasNext()) { + MarkPassing markPassing = mock(MarkPassing.class); + when(markPassing.getTimePoint()).thenReturn(endOfRace); + Competitor competitor = competitorIterator.next(); + when(markPassing.getCompetitor()).thenReturn(competitor); + when(trackedRace.getMarkPassing(competitor, waypoint)).thenReturn(markPassing); + markPassings.add(markPassing); + } + when(trackedRace.getMarkPassingsInOrder(waypoint)).thenReturn(markPassings); + } + private MarkPassing createMockedStartMarkPassing() { Calendar cal = Calendar.getInstance(); cal.set(2014, 4, 3, 12, 15); @@ -193,9 +235,11 @@ public class PolarDataMinerTest { return passing; } - private RaceDefinition createMockedRaceDefinition() { + private RaceDefinition createMockedRaceDefinition(Waypoint startWaypoint, Waypoint finishWaypoint) { RaceDefinition raceDefinition = mock(RaceDefinition.class); Course mockedCourse = createMockedCourse(); + when(mockedCourse.getLastWaypoint()).thenReturn(finishWaypoint); + when(mockedCourse.getFirstWaypoint()).thenReturn(startWaypoint); when(raceDefinition.getCourse()).thenReturn(mockedCourse); BoatClass mockedBoatClass = mock(BoatClass.class); when(mockedBoatClass.getManeuverDegreeAngleThreshold()).thenReturn(20.0); diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLeastSquaresTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLeastSquaresTest.java new file mode 100644 index 00000000000..55e87027925 --- /dev/null +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLeastSquaresTest.java @@ -0,0 +1,196 @@ +package com.sap.sailing.polars.regression.test; + +import static org.hamcrest.Matchers.closeTo; +import static org.junit.Assert.assertThat; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.Random; + +import org.junit.Test; + +import com.sap.sailing.polars.regression.IncrementalLeastSquares; +import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; +import com.sap.sailing.polars.regression.impl.IncrementalAnyOrderLeastSquaresImpl; + +public class IncrementalLeastSquaresTest { + + + private static final double ERROR = 0.01; + + @Test + public void testIncrementalRegression() throws NotEnoughDataHasBeenAddedException { + IncrementalLeastSquares regression = new IncrementalAnyOrderLeastSquaresImpl(3); + long startTime = System.nanoTime(); + Random random = new Random(1); + for (int i = 0; i < 100000; i++) { + double x = random.nextDouble() * 40; + regression.addData(x, function(x) + (random.nextDouble() - 0.5)); + } + System.out.println("Adding Data " + (System.nanoTime() - startTime) + "ns"); + startTime = System.nanoTime(); + double[] coeffs = regression.getOrCreatePolynomialFunction().getCoefficients(); + System.out.println("First calculation " + (System.nanoTime() - startTime) + "ns"); + + startTime = System.nanoTime(); + coeffs = regression.getOrCreatePolynomialFunction().getCoefficients(); + System.out.println("No calculation " + (System.nanoTime() - startTime) + "ns"); + double x = random.nextDouble() * 40; + regression.addData(x, function(x) + (random.nextDouble() - 0.5)); + startTime = System.nanoTime() ; + coeffs = regression.getOrCreatePolynomialFunction().getCoefficients(); + System.out.println("Second calculation " + (System.nanoTime() - startTime) + "ns"); + + assertThat(coeffs[0], closeTo(-2.1309, ERROR)); + assertThat(coeffs[1], closeTo(1.5213, ERROR)); + assertThat(coeffs[2], closeTo(-0.0373, ERROR)); + assertThat(coeffs[3], closeTo(0.0003, ERROR)); + } + + private double function(double x) { + return 0.0003*x*x*x-0.0373*x*x+1.5213*x -2.1309; + } + + @Test + public void testIncrementalRegressionWithWebsiteExampleData() throws NotEnoughDataHasBeenAddedException { + IncrementalLeastSquares regression = new IncrementalAnyOrderLeastSquaresImpl(4); + regression.addData(-1, -1); + regression.addData(0, 3); + regression.addData(1, 2.5); + regression.addData(2, 5); + regression.addData(3, 4); + regression.addData(5, 2); + regression.addData(7, 5); + regression.addData(9, 4); + double[] coeffs = regression.getOrCreatePolynomialFunction().getCoefficients(); + assertThat(coeffs[0], closeTo(2.686, ERROR)); + assertThat(coeffs[1], closeTo(2.301, ERROR)); + assertThat(coeffs[2], closeTo(-1.233, ERROR)); + assertThat(coeffs[3], closeTo(0.2169 , ERROR)); + assertThat(coeffs[4], closeTo(-0.01182 , ERROR)); + } + + @Test + public void testIncrementalRegressionWithRealData() throws NotEnoughDataHasBeenAddedException { + + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(3); + fillRegression(leastSquares); + double[] coeffs = leastSquares.getOrCreatePolynomialFunction().getCoefficients(); + + assertThat(coeffs[0], closeTo(6.319899225, ERROR)); + assertThat(coeffs[1], closeTo(-0.427890096, ERROR)); + assertThat(coeffs[2], closeTo(0.057763706, ERROR)); + assertThat(coeffs[3], closeTo(-0.001574209 , ERROR)); + } + + @Test + public void testIncrementalRegressionWithRealDataNoInterceptWithSymbollicInversion() throws NotEnoughDataHasBeenAddedException { + + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(3, false); + fillRegression(leastSquares); + double[] coeffs = leastSquares.getOrCreatePolynomialFunction().getCoefficients(); + + assertThat(coeffs[0], closeTo(0, ERROR)); + assertThat(coeffs[1], closeTo(0.9317759507, ERROR)); + assertThat(coeffs[2], closeTo(-0.0364029315, ERROR)); + assertThat(coeffs[3], closeTo(0.0005326266, ERROR)); + + leastSquares.addData(0, 0); + long startTime = System.nanoTime(); + leastSquares.getOrCreatePolynomialFunction().getCoefficients(); + System.out.println("With symbollic inversion no intercept" + (System.nanoTime() - startTime) + "ns"); + } + + @Test + public void testIncrementalRegressionWithRealDataNoInterceptWithoutSymbollicInversion() throws NotEnoughDataHasBeenAddedException { + + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(3, false, false); + fillRegression(leastSquares); + double[] coeffs = leastSquares.getOrCreatePolynomialFunction().getCoefficients(); + + assertThat(coeffs[0], closeTo(0, ERROR)); + assertThat(coeffs[1], closeTo(0.9317759507, ERROR)); + assertThat(coeffs[2], closeTo(-0.0364029315, ERROR)); + assertThat(coeffs[3], closeTo(0.0005326266, ERROR)); + + leastSquares.addData(0, 0); + long startTime = System.nanoTime(); + leastSquares.getOrCreatePolynomialFunction().getCoefficients(); + System.out.println("With numeric inversion no intercept " + (System.nanoTime() - startTime) + "ns"); + } + + @Test + public void testIncrementalRegressionWithRealDataWithInterceptWithSymbollicInversion() throws NotEnoughDataHasBeenAddedException { + + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(3, true, true); + fillRegression(leastSquares); + double[] coeffs = leastSquares.getOrCreatePolynomialFunction().getCoefficients(); + + assertThat(coeffs[0], closeTo(6.319899225, ERROR)); + assertThat(coeffs[1], closeTo(-0.427890096, ERROR)); + assertThat(coeffs[2], closeTo(0.057763706, ERROR)); + assertThat(coeffs[3], closeTo(-0.001574209, ERROR)); + + leastSquares.addData(0, 0); + long startTime = System.nanoTime(); + leastSquares.getOrCreatePolynomialFunction().getCoefficients(); + System.out.println("With symbollic inversion and intercept " + (System.nanoTime() - startTime) + "ns"); + } + + @Test + public void testIncrementalRegressionWithRealDataWithInterceptWithoutSymbollicInversion() throws NotEnoughDataHasBeenAddedException { + + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(3, true, false); + fillRegression(leastSquares); + double[] coeffs = leastSquares.getOrCreatePolynomialFunction().getCoefficients(); + + assertThat(coeffs[0], closeTo(6.319899225, ERROR)); + assertThat(coeffs[1], closeTo(-0.427890096, ERROR)); + assertThat(coeffs[2], closeTo(0.057763706, ERROR)); + assertThat(coeffs[3], closeTo(-0.001574209, ERROR)); + + leastSquares.addData(0, 0); + long startTime = System.nanoTime(); + leastSquares.getOrCreatePolynomialFunction().getCoefficients(); + System.out.println("With numeric inversion and intercept " + (System.nanoTime() - startTime) + "ns"); + } + + private void fillRegression(IncrementalLeastSquares leastSquares) { + File file = new File("resources/5O5Worlds14_Winners_Upwind4550.csv"); + BufferedReader br = null; + String line = ""; + String cvsSplitBy = ","; + + try { + + br = new BufferedReader(new FileReader(file)); + while ((line = br.readLine()) != null) { + + // use comma as separator + String[] row = line.split(cvsSplitBy); + leastSquares.addData(Double.parseDouble(row[0]), Double.parseDouble(row[1])); + } + + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (br != null) { + try { + br.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + + + + +} diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLinearRegressionTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLinearRegressionOrderOneTest.java similarity index 55% rename from java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLinearRegressionTest.java rename to java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLinearRegressionOrderOneTest.java index 05823d6064f..b8bf40b0297 100644 --- a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLinearRegressionTest.java +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLinearRegressionOrderOneTest.java @@ -7,149 +7,139 @@ import static org.junit.Assert.assertTrue; import org.hamcrest.number.IsCloseTo; import org.junit.Test; -import com.sap.sailing.polars.regression.IncrementalLinearRegressionProcessor; +import com.sap.sailing.polars.regression.IncrementalLeastSquares; import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; -import com.sap.sailing.polars.regression.impl.IncrementalLeastSquaresProcessor; +import com.sap.sailing.polars.regression.impl.IncrementalAnyOrderLeastSquaresImpl; -public class IncrementalLinearRegressionTest { +public class IncrementalLinearRegressionOrderOneTest { private static double EPSILON = 1E-12; private static double VAGUE_EPSILON = 0.1; - private IncrementalLinearRegressionProcessor createRegressionProcessor() { - IncrementalLinearRegressionProcessor processor = new IncrementalLeastSquaresProcessor(); - return processor; - } - @Test public void testRegressionWithTwoDataPoints() throws NotEnoughDataHasBeenAddedException { - IncrementalLinearRegressionProcessor processor = createRegressionProcessor(); - + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1); double x1 = 3; double y1 = 7; - processor.addMeasuredPoint(x1, y1); + leastSquares.addData(x1, y1); double x2 = 6; double y2 = 14; - processor.addMeasuredPoint(x2, y2); + leastSquares.addData(x2, y2); - double y = processor.getEstimatedY(x1); + double y = leastSquares.getOrCreatePolynomialFunction().value(x1); assertThat(y, new IsCloseTo(y1, EPSILON)); - y = processor.getEstimatedY(x2); + y = leastSquares.getOrCreatePolynomialFunction().value(x2); assertThat(y, new IsCloseTo(y2, EPSILON)); - y = processor.getEstimatedY(4.5); + y = leastSquares.getOrCreatePolynomialFunction().value(4.5); assertThat(y, new IsCloseTo(10.5, EPSILON)); } @Test public void testRegressionWithMultipleDataPoints() throws NotEnoughDataHasBeenAddedException { - IncrementalLinearRegressionProcessor processor = createRegressionProcessor(); + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1); for (int i = -50; i < 51; i++) { - processor.addMeasuredPoint(i, i); + leastSquares.addData(i, i); } - double y = processor.getEstimatedY(-2); + double y = leastSquares.getOrCreatePolynomialFunction().value(-2); assertThat(y, new IsCloseTo(-2, EPSILON)); // 2 Points off the line - processor.addMeasuredPoint(-8, -3); - processor.addMeasuredPoint(8, -3); + leastSquares.addData(-8, -3); + leastSquares.addData(8, -3); - y = processor.getEstimatedY(-2); + y = leastSquares.getOrCreatePolynomialFunction().value(-2); assertThat(y, new IsCloseTo(-2, VAGUE_EPSILON)); - y = processor.getEstimatedY(20); + y = leastSquares.getOrCreatePolynomialFunction().value(20); assertThat(y, new IsCloseTo(20, VAGUE_EPSILON)); } @Test public void testRegressionWithABigAmountOfDataPoints() throws NotEnoughDataHasBeenAddedException { - IncrementalLinearRegressionProcessor processor = createRegressionProcessor(); + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1); for (int i = -1000; i < 1000; i++) { // For every even number + 1, else - 1 double y = i % 2 == 0 ? i + 1 : i - 1; - processor.addMeasuredPoint(i, y); + leastSquares.addData(i, y); } - double y = processor.getEstimatedY(-2); + double y = leastSquares.getOrCreatePolynomialFunction().value(-2); assertThat(y, new IsCloseTo(-2, VAGUE_EPSILON)); - y = processor.getEstimatedY(20); + y = leastSquares.getOrCreatePolynomialFunction().value(20); assertThat(y, new IsCloseTo(20, VAGUE_EPSILON)); } @Test public void testRegressionWithZeroValueData() throws NotEnoughDataHasBeenAddedException { - IncrementalLinearRegressionProcessor processor = createRegressionProcessor(); + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1); double x1 = 0; double y1 = 0; - processor.addMeasuredPoint(x1, y1); + leastSquares.addData(x1, y1); double x2 = 5; double y2 = 0; - processor.addMeasuredPoint(x2, y2); + leastSquares.addData(x2, y2); - double y = processor.getEstimatedY(x1); + double y = leastSquares.getOrCreatePolynomialFunction().value(x1); assertThat(y, new IsCloseTo(y1, EPSILON)); - y = processor.getEstimatedY(x2); + y = leastSquares.getOrCreatePolynomialFunction().value(x2); assertThat(y, new IsCloseTo(y2, EPSILON)); } @Test public void testRegressionWithNegativeData() throws NotEnoughDataHasBeenAddedException { - IncrementalLinearRegressionProcessor processor = createRegressionProcessor(); + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1); double x1 = -5; double y1 = -5; - processor.addMeasuredPoint(x1, y1); + leastSquares.addData(x1, y1); double x2 = 5; double y2 = 5; - processor.addMeasuredPoint(x2, y2); + leastSquares.addData(x2, y2); - double y = processor.getEstimatedY(x1); + double y = leastSquares.getOrCreatePolynomialFunction().value(x1); assertThat(y, new IsCloseTo(y1, EPSILON)); - y = processor.getEstimatedY(x2); + y = leastSquares.getOrCreatePolynomialFunction().value(x2); assertThat(y, new IsCloseTo(y2, EPSILON)); - y = processor.getEstimatedY(0); + y = leastSquares.getOrCreatePolynomialFunction().value(0); assertThat(y, new IsCloseTo(0, EPSILON)); } @Test public void testRegressionWithSmallValuedData() throws NotEnoughDataHasBeenAddedException { - IncrementalLinearRegressionProcessor processor = createRegressionProcessor(); + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1); double x1 = 5E-9; double y1 = 5E-9; for (int i = 0; i < 1000; i++) { double yTimesI = y1 * i; double xTimesI = x1 * i; - processor.addMeasuredPoint(xTimesI, yTimesI); - if (i > 0) { - double y = processor.getEstimatedY(xTimesI); - assertThat(y, new IsCloseTo(yTimesI, EPSILON)); - } + leastSquares.addData(xTimesI, yTimesI); } // Add data that is off the line but offsetting each other - processor.addMeasuredPoint(-x1, y1); - processor.addMeasuredPoint(x1, -y1); + leastSquares.addData(-x1, y1); + leastSquares.addData(x1, -y1); - double y = processor.getEstimatedY(0); + double y = leastSquares.getOrCreatePolynomialFunction().value(0); assertThat(y, new IsCloseTo(0, EPSILON)); } @Test(expected = NotEnoughDataHasBeenAddedException.class) public void testNoDataExceptionThrowing() throws NotEnoughDataHasBeenAddedException { - IncrementalLinearRegressionProcessor processor = createRegressionProcessor(); - processor.getEstimatedY(15); + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1); + leastSquares.getOrCreatePolynomialFunction().value(15); } /** @@ -159,13 +149,13 @@ public class IncrementalLinearRegressionTest { */ @Test public void testRegressionWithOneDataPoint() throws NotEnoughDataHasBeenAddedException { - IncrementalLinearRegressionProcessor processor = createRegressionProcessor(); + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1); double x1 = 3; double y1 = 5; - processor.addMeasuredPoint(x1, y1); + leastSquares.addData(x1, y1); - double y = processor.getEstimatedY(20); + double y = leastSquares.getOrCreatePolynomialFunction().value(20); assertThat(y, is(y1)); } @@ -196,37 +186,37 @@ public class IncrementalLinearRegressionTest { @Test public void testNorris() throws NotEnoughDataHasBeenAddedException { - IncrementalLinearRegressionProcessor regression = createRegressionProcessor(); + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1); for (int i = 0; i < data.length; i++) { - regression.addMeasuredPoint(data[i][1], data[i][0]); + leastSquares.addData(data[i][1], data[i][0]); } // Tests against certified values from // http://www.itl.nist.gov/div898/strd/lls/data/LINKS/DATA/Norris.dat - assertThat(regression.getSlope(), new IsCloseTo(1.00211681802045, EPSILON)); - assertThat(regression.getIntercept(), new IsCloseTo(-0.262323073774029, EPSILON)); + assertThat(leastSquares.getOrCreatePolynomialFunction().getCoefficients()[1], new IsCloseTo(1.00211681802045, EPSILON)); + assertThat(leastSquares.getOrCreatePolynomialFunction().getCoefficients()[0], new IsCloseTo(-0.262323073774029, EPSILON)); // ------------ End certified data tests - assertThat(regression.getEstimatedY(0), new IsCloseTo(-0.262323073774029, EPSILON)); - assertThat(regression.getEstimatedY(1), new IsCloseTo(1.00211681802045 - 0.26232307377402, EPSILON)); + assertThat(leastSquares.getOrCreatePolynomialFunction().value(0), new IsCloseTo(-0.262323073774029, EPSILON)); + assertThat(leastSquares.getOrCreatePolynomialFunction().value(1), new IsCloseTo(1.00211681802045 - 0.26232307377402, EPSILON)); } @Test - public void testPerfect() { - IncrementalLinearRegressionProcessor regression = createRegressionProcessor(); + public void testPerfect() throws NotEnoughDataHasBeenAddedException { + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1); int n = 100; for (int i = 0; i < n; i++) { - regression.addMeasuredPoint(((double) i) / (n - 1), i); + leastSquares.addData(((double) i) / (n - 1), i); } - assertTrue(regression.getSlope() > 0); + assertTrue(leastSquares.getOrCreatePolynomialFunction().getCoefficients()[1] > 0); } @Test - public void testPerfectNegative() { - IncrementalLinearRegressionProcessor regression = createRegressionProcessor(); + public void testPerfectNegative() throws NotEnoughDataHasBeenAddedException { + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1); int n = 100; for (int i = 0; i < n; i++) { - regression.addMeasuredPoint(-((double) i) / (n - 1), i); + leastSquares.addData(-((double) i) / (n - 1), i); } - assertTrue(regression.getSlope() < 0); + assertTrue(leastSquares.getOrCreatePolynomialFunction().getCoefficients()[1] < 0); } diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/test/PolarSheetAnalyzerTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/test/PolarSheetAnalyzerTest.java deleted file mode 100755 index b0549f036cb..00000000000 --- a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/test/PolarSheetAnalyzerTest.java +++ /dev/null @@ -1,185 +0,0 @@ -package com.sap.sailing.polars.test; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.closeTo; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.argThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import org.junit.Test; -import org.mockito.ArgumentMatcher; - -import com.sap.sailing.domain.base.BoatClass; -import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; -import com.sap.sailing.domain.base.impl.SpeedWithConfidenceImpl; -import com.sap.sailing.domain.common.Bearing; -import com.sap.sailing.domain.common.LegType; -import com.sap.sailing.domain.common.Speed; -import com.sap.sailing.domain.common.Tack; -import com.sap.sailing.domain.common.impl.KnotSpeedImpl; -import com.sap.sailing.polars.PolarDataService; -import com.sap.sailing.polars.analysis.PolarSheetAnalyzer; -import com.sap.sailing.polars.analysis.impl.PolarSheetAnalyzerImpl; -import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; - -public class PolarSheetAnalyzerTest { - - @Test - public void testSpeedAndBearingCalculation() throws NotEnoughDataHasBeenAddedException { - - PolarSheetAnalyzer analyzer = new PolarSheetAnalyzerImpl(createMockedPolarDataService()); - BoatClass boatClass = mock(BoatClass.class); - - SpeedWithBearingWithConfidence result = analyzer.getAverageSpeedAndCourseOverGround(boatClass, - new KnotSpeedImpl(14), LegType.UPWIND, Tack.STARBOARD); - assertThat(result.getObject().getKnots(), closeTo(8.5, 0.1)); - assertThat(result.getObject().getBearing().getDegrees(), closeTo(50, 0.1)); - assertThat(result.getConfidence(), closeTo(0.7, 0.1)); - - SpeedWithBearingWithConfidence result2 = analyzer.getAverageSpeedAndCourseOverGround(boatClass, - new KnotSpeedImpl(14), LegType.DOWNWIND, Tack.STARBOARD); - assertThat(result2.getObject().getKnots(), closeTo(14.2, 0.1)); - assertThat(result2.getObject().getBearing().getDegrees(), closeTo(142.8, 0.1)); - assertThat(result2.getConfidence(), closeTo(0.6, 0.1)); - - SpeedWithBearingWithConfidence result3 = analyzer.getAverageSpeedAndCourseOverGround(boatClass, - new KnotSpeedImpl(14), LegType.UPWIND, Tack.PORT); - assertThat(result3.getObject().getKnots(), closeTo(8.6, 0.1)); - assertThat(result3.getObject().getBearing().getDegrees(), closeTo(-49.5, 0.1)); - assertThat(result3.getConfidence(), closeTo(0.7, 0.1)); - - SpeedWithBearingWithConfidence result4 = analyzer.getAverageSpeedAndCourseOverGround(boatClass, - new KnotSpeedImpl(14), LegType.DOWNWIND, Tack.PORT); - assertThat(result4.getObject().getKnots(), closeTo(13.5, 0.1)); - assertThat(result4.getObject().getBearing().getDegrees(), closeTo(-143.6, 0.1)); - assertThat(result4.getConfidence(), closeTo(0.25, 0.1)); - - } - - private PolarDataService createMockedPolarDataService() throws NotEnoughDataHasBeenAddedException { - PolarDataService mockedPolarDataService = mock(PolarDataService.class); - int[] data = createCounts(); - when(mockedPolarDataService.getDataCountsForWindSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)), - any(Integer.class), any(Integer.class))).thenReturn(data); - - //Starboard Upwind - when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)), - argThat(new BearingMatcher(49)))).thenReturn( - new SpeedWithConfidenceImpl(new KnotSpeedImpl(8.478048671702888), 0.5, null)); - when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)), - argThat(new BearingMatcher(50)))).thenReturn( - new SpeedWithConfidenceImpl(new KnotSpeedImpl(8.466303997538812), 0.5, null)); - when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)), - argThat(new BearingMatcher(51)))).thenReturn( - new SpeedWithConfidenceImpl(new KnotSpeedImpl(8.583383077026435), 0.5, null)); - - //Starboard Downwind - when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)), - argThat(new BearingMatcher(141)))).thenReturn( - new SpeedWithConfidenceImpl(new KnotSpeedImpl(13.794354053931528), 0.5, null)); - when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)), - argThat(new BearingMatcher(142)))).thenReturn( - new SpeedWithConfidenceImpl(new KnotSpeedImpl(13.665782579628802), 0.5, null)); - when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)), - argThat(new BearingMatcher(143)))).thenReturn( - new SpeedWithConfidenceImpl(new KnotSpeedImpl(14.169301888320263), 0.5, null)); - - //Port Upwind - when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)), - argThat(new BearingMatcher(-48)))).thenReturn( - new SpeedWithConfidenceImpl(new KnotSpeedImpl(8.445599410456111), 0.5, null)); - when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)), - argThat(new BearingMatcher(-50)))).thenReturn( - new SpeedWithConfidenceImpl(new KnotSpeedImpl(8.553274292235153), 0.5, null)); - when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)), - argThat(new BearingMatcher(-49)))).thenReturn( - new SpeedWithConfidenceImpl(new KnotSpeedImpl(8.614582090896583), 0.5, null)); - - //Port Downwind - when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)), - argThat(new BearingMatcher(-145)))).thenReturn( - new SpeedWithConfidenceImpl(new KnotSpeedImpl(13.78894715705271), 0.5, null)); - when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)), - argThat(new BearingMatcher(-144)))).thenReturn( - new SpeedWithConfidenceImpl(new KnotSpeedImpl(13.420656294986587), 0.5, null)); - when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)), - argThat(new BearingMatcher(-143)))).thenReturn( - new SpeedWithConfidenceImpl(new KnotSpeedImpl(13.268607457651942), 0.5, null)); - - return mockedPolarDataService; - } - - /** - * The following data is taken from a real race. (49ER yellow R2, KW 2014) - * Only the data for windspeed 14kn is used. The first block contains the - * boatspeeds for every angle to the wind. The second block contains the - * datacount for each angle (number of underlying fixes). - * - * @return - */ - private int[] createCounts() { - int[] dataCountsPerAngle = { 0, 0, 2, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 3, 1, 2, 1, 6, 2, 2, 2, 1, 6, 6, 17, - 13, 19, 30, 43, 32, 50, 48, 57, 70, 76, 107, 129, 177, 186, 222, 237, 275, 285, 321, 322, 401, 451, - 466, 413, 468, 479, 459, 429, 360, 345, 317, 288, 278, 202, 175, 130, 113, 104, 100, 73, 45, 51, 52, - 37, 40, 36, 26, 23, 32, 33, 29, 29, 22, 24, 27, 23, 22, 23, 22, 21, 15, 18, 20, 14, 15, 21, 20, 19, 6, - 12, 10, 9, 10, 8, 1, 9, 6, 6, 14, 16, 7, 7, 6, 2, 5, 3, 6, 5, 8, 7, 14, 14, 12, 9, 20, 14, 22, 28, 14, - 20, 22, 29, 30, 38, 45, 50, 72, 87, 117, 114, 110, 145, 148, 156, 165, 150, 194, 224, 211, 196, 199, - 198, 182, 155, 125, 85, 69, 65, 49, 46, 61, 47, 48, 51, 45, 21, 14, 8, 9, 8, 8, 3, 7, 5, 2, 10, 8, 7, - 7, 10, 8, 13, 13, 11, 5, 9, 9, 8, 15, 6, 3, 5, 11, 9, 12, 5, 13, 12, 18, 15, 31, 25, 35, 36, 39, 59, - 71, 55, 52, 64, 72, 66, 70, 89, 104, 81, 120, 130, 117, 107, 113, 137, 136, 126, 108, 103, 92, 85, 76, - 65, 75, 75, 70, 57, 49, 49, 35, 44, 35, 18, 22, 17, 18, 10, 6, 4, 10, 9, 10, 8, 4, 4, 4, 10, 4, 4, 4, - 9, 8, 8, 7, 7, 6, 4, 7, 4, 1, 6, 6, 4, 6, 5, 5, 5, 6, 5, 9, 7, 12, 16, 10, 25, 25, 30, 29, 47, 44, 31, - 69, 68, 79, 62, 66, 86, 72, 84, 89, 75, 73, 62, 104, 110, 147, 137, 170, 204, 310, 347, 354, 377, 433, - 457, 419, 436, 467, 502, 513, 525, 463, 422, 405, 363, 309, 282, 262, 186, 162, 123, 97, 81, 45, 35, - 30, 24, 20, 12, 9, 8, 5, 6, 4, 6, 7, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 1 }; - return dataCountsPerAngle; - } - - private class SpeedMatcher extends ArgumentMatcher { - - private final double speedToMatchInKnots; - - public SpeedMatcher(double speedToMatchInKnots) { - this.speedToMatchInKnots = speedToMatchInKnots; - } - - - @Override - public boolean matches(Object argument) { - boolean result = false; - if (argument != null) { - Speed speed = (Speed) argument; - if (speed.getKnots() > speedToMatchInKnots - 0.05 && speed.getKnots() < speedToMatchInKnots + 0.05) { - result = true; - } - } - return result; - } - - } - - private class BearingMatcher extends ArgumentMatcher { - - private final double bearingToMatchInDegrees; - - public BearingMatcher(double bearingToMatchInDegrees) { - this.bearingToMatchInDegrees = bearingToMatchInDegrees; - } - - @Override - public boolean matches(Object argument) { - boolean result = false; - if (argument != null) { - Bearing bearing = (Bearing) argument; - if (bearing.getDegrees() > bearingToMatchInDegrees - 0.4999999 - && bearing.getDegrees() < bearingToMatchInDegrees + 0.49999999) { - result = true; - } - } - return result; - } - - } - -} diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/test/PolarSheetGenerationTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/test/PolarSheetGenerationTest.java index 31622df7ab3..3c1dfb68f7c 100644 --- a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/test/PolarSheetGenerationTest.java +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/test/PolarSheetGenerationTest.java @@ -81,7 +81,7 @@ public class PolarSheetGenerationTest { double[] levels = { 4., 6., 8., 10., 12., 14., 16., 20., 25., 30. }; WindSpeedSteppingWithMaxDistance windStepping = new WindSpeedSteppingWithMaxDistance(levels, 2.0); PolarSheetGenerationSettings settings = new PolarSheetGenerationSettingsImpl(1, 0, 1, 20, 0, false, true, 5, - 0.05, false, windStepping, false); + 0.05, false, windStepping, false, 0); TimePoint startTime = new MillisecondsTimePoint(9); TimePoint endTime = new MillisecondsTimePoint(80); @@ -91,8 +91,8 @@ public class PolarSheetGenerationTest { settings, executor); BoatClass forelle = new BoatClassImpl("Forelle", true); - Competitor competitor = new CompetitorImpl(UUID.randomUUID(), "Hans Frantz", Color.RED, new TeamImpl("SAP", null, null), - new BoatImpl("Schnelle Forelle", forelle, "GER000")); + Competitor competitor = new CompetitorImpl(UUID.randomUUID(), "Hans Frantz", Color.RED, null, + new TeamImpl("SAP", null, null), new BoatImpl("Schnelle Forelle", forelle, "GER000")); PolarFixAggregationWorker task = new PolarFixAggregationWorker(race, resultContainer, startTime, endTime, @@ -121,7 +121,7 @@ public class PolarSheetGenerationTest { double[] levels = { 4., 6., 8., 10., 12., 14., 16., 20., 25., 30. }; WindSpeedSteppingWithMaxDistance windStepping = new WindSpeedSteppingWithMaxDistance(levels, 2.0); PolarSheetGenerationSettings settings = new PolarSheetGenerationSettingsImpl(1, 0, 1, 10, 0, false, true, 5, - 0.05, false, windStepping, false); + 0.05, false, windStepping, false, 0); PolarSheetHistogramBuilder builder = new PolarSheetHistogramBuilder(settings); diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/windestimation/TestWindEstimationFromManeuversOnAFew505Races.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/windestimation/TestWindEstimationFromManeuversOnAFew505Races.java index d30eedf5b49..c6f98ebc045 100755 --- a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/windestimation/TestWindEstimationFromManeuversOnAFew505Races.java +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/windestimation/TestWindEstimationFromManeuversOnAFew505Races.java @@ -82,7 +82,7 @@ public class TestWindEstimationFromManeuversOnAFew505Races extends OnlineTracTra CreateModelException, NotEnoughDataHasBeenAddedException { setUp("OBMR 2012", "event_20121031_OBMR-OBMR_2012_VR_Fr_Fleet_1_2"); Wind average = getManeuverBasedAverageWind(); - assertEquals(222, average.getFrom().getDegrees(), 5.0); + assertEquals(222, average.getFrom().getDegrees(), 20.0); // wind is shifty in this race and goes from 195..245 (measured) } @Test diff --git a/java/com.sap.sailing.polars/META-INF/MANIFEST.MF b/java/com.sap.sailing.polars/META-INF/MANIFEST.MF index 5b89143f5c8..74883dc9799 100644 --- a/java/com.sap.sailing.polars/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.polars/META-INF/MANIFEST.MF @@ -8,7 +8,6 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Bundle-ClassPath: . Export-Package: com.sap.sailing.polars, - com.sap.sailing.polars.analysis, com.sap.sailing.polars.data, com.sap.sailing.polars.factory, com.sap.sailing.polars.regression, @@ -21,7 +20,8 @@ Require-Bundle: com.sap.sse.datamining.shared;bundle-version="1.0.0", com.sap.sse.common, com.sap.sse, - com.sap.sse.shared.android + com.sap.sse.shared.android, + org.apache.commons.math;bundle-version="2.1.0" Import-Package: com.sap.sailing.util, com.sap.sse.common diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/PolarDataService.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/PolarDataService.java index f11d2fc38cb..96e04676306 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/PolarDataService.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/PolarDataService.java @@ -4,6 +4,8 @@ import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; +import org.apache.commons.math.analysis.polynomials.PolynomialFunction; + import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; @@ -17,7 +19,7 @@ import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.Tack; import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.TrackedRace; -import com.sap.sailing.polars.analysis.PolarSheetAnalyzer; +import com.sap.sailing.polars.mining.MovingAverageProcessor; import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; import com.sap.sse.common.Util.Pair; @@ -26,14 +28,14 @@ import com.sap.sse.common.Util.Pair; * (sometimes also referred to as a "VPP" (velocity prediction program)) makes a prediction how fast the boat will * sail at a given true wind angle and a given true wind speed.

    * - * This service uses a {@link PolarSheetAnalyzer} for more advanced analysis. Its methods are facaded in this interface for + * This service uses a {@link MovingAverageProcessor} for more advanced analysis. Its methods are facaded in this interface for * central access.

    * The interesting methods for a client are {@link #getSpeed(BoatClass, Speed, Bearing, boolean)} if data for a specific angle is - * needed and {@link #getAverageSpeedWithBearing(BoatClass, Speed, LegType, Tack)} + * needed and {@link #getAverageSpeedWithBearing(BoatClass, Speed, LegType, Tack, boolean)} * which also returns the average angle for the parameters provided. * * @author Frederik Petersen (D054528) - * @autho Axel Uhl (D043530) + * @author Axel Uhl (D043530) * */ public interface PolarDataService { @@ -59,6 +61,7 @@ public interface PolarDataService { * courses yet. Use getSpeed for the desired angle to get rawer information on other courses for now. * @param tack * Polar data can vary depending on the tack the boat is on. + * @param useRegressionForSpeed TODO * @return The estimated average speed of a boat for the supplied parameters with the estimated average bearing to * the true wind and a confidence which consists of the confidences of the wind speed, and boat speed sources (50%) * and a confidence calculated using the amount of underlying fixes (50%). 0 <= confidence < 1
    @@ -77,7 +80,7 @@ public interface PolarDataService { * If there is not enough data to supply a value with some kind of significance. */ SpeedWithBearingWithConfidence getAverageSpeedWithBearing(BoatClass boatClass, Speed windSpeed, - LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException; + LegType legType, Tack tack, boolean useRegressionForSpeed) throws NotEnoughDataHasBeenAddedException; /** @@ -164,4 +167,30 @@ public interface PolarDataService { * speed and true wind angle in the {@link Pair#getB() second} component. */ Pair> getManeuverLikelihoodAndTwsTwa(BoatClass boatClass, Speed speedOverGround, double courseChangeDeg, ManeuverType maneuverType); + + /** + * This method is not intended to be used directly apart from debugging purposes. If you intend to use the polar service please + * use the {@link #getAverageSpeedWithBearing(BoatClass, Speed, LegType, Tack, boolean)} method. + * + * @param boatClass + * @param legType + * @param tack + * @return The estimating function for the tack and legtype combination estimating boatspeed over windspeed for the + * given boat class. All values in kn. + * @throws NotEnoughDataHasBeenAddedException + */ + PolynomialFunction getSpeedRegressionFunction(BoatClass boatClass, LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException; + + /** + * This method is not intended to be used directly apart from debugging purposes. If you intend to use the polar service please + * use the {@link #getAverageSpeedWithBearing(BoatClass, Speed, LegType, Tack, boolean)} method. + * + * @param boatClass + * @param legType + * @param tack + * @return The estimating function for the tack and legtype combination estimating true wind angle over windspeed for the + * given boat class. TWA in degrees and windspeeds in knots. + * @throws NotEnoughDataHasBeenAddedException + */ + PolynomialFunction getAngleRegressionFunction(BoatClass boatClass, LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException; } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/analysis/impl/PolarSheetAnalyzerImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/analysis/impl/PolarSheetAnalyzerImpl.java deleted file mode 100755 index d32bcbf1926..00000000000 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/analysis/impl/PolarSheetAnalyzerImpl.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.sap.sailing.polars.analysis.impl; - -import com.sap.sailing.domain.base.BoatClass; -import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; -import com.sap.sailing.domain.base.SpeedWithConfidence; -import com.sap.sailing.domain.base.impl.SpeedWithBearingWithConfidenceImpl; -import com.sap.sailing.domain.common.Bearing; -import com.sap.sailing.domain.common.LegType; -import com.sap.sailing.domain.common.Speed; -import com.sap.sailing.domain.common.SpeedWithBearing; -import com.sap.sailing.domain.common.Tack; -import com.sap.sailing.domain.common.impl.DegreeBearingImpl; -import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; -import com.sap.sailing.polars.PolarDataService; -import com.sap.sailing.polars.analysis.PolarSheetAnalyzer; -import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; - -/** - * Extracts typical measures from polar sheets. - * - * @author Frederik Petersen (D054528) - * - */ -public class PolarSheetAnalyzerImpl implements PolarSheetAnalyzer { - - private static final int MINIMUM_DATA_COUNT_FOR_ONE_ANGLE = 5; - private final PolarDataService polarDataService; - - public PolarSheetAnalyzerImpl(PolarDataService polarDataService) { - this.polarDataService = polarDataService; - } - - @Override - public SpeedWithBearingWithConfidence getAverageSpeedAndCourseOverGround(BoatClass boatClass, Speed windSpeed, LegType legType, Tack tack) - throws NotEnoughDataHasBeenAddedException { - final int startAngleInclusive; - final int endAngleExclusive; - if (legType == LegType.UPWIND && tack == Tack.STARBOARD) { - startAngleInclusive = 0; - endAngleExclusive = 90; - } else if (legType == LegType.DOWNWIND && tack == Tack.STARBOARD) { - startAngleInclusive = 90; - endAngleExclusive = 180; - } else if (legType == LegType.DOWNWIND && tack == Tack.PORT) { - startAngleInclusive = 180; - endAngleExclusive = 270; - } else if (legType == LegType.UPWIND && tack == Tack.PORT) { - startAngleInclusive = 270; - endAngleExclusive = 360; - } else { - throw new IllegalArgumentException("Leg type must be "+LegType.UPWIND.name()+ - " or "+LegType.DOWNWIND.name()+" but was "+legType.name()); - } - SpeedWithBearingWithConfidence speedWithBearing = estimateAnglePeakAndAverageSpeed(boatClass, windSpeed, startAngleInclusive, - endAngleExclusive); - return speedWithBearing; - } - - private SpeedWithBearingWithConfidence estimateAnglePeakAndAverageSpeed(BoatClass boatClass, Speed windSpeed, - int startAngleInclusive, int endAngleExclusive) throws NotEnoughDataHasBeenAddedException { - int[] dataCountPerAngle = getDataCountArray(boatClass, windSpeed, startAngleInclusive, endAngleExclusive); - double estimatedPeak = estimatePeak(boatClass, windSpeed, startAngleInclusive, endAngleExclusive, dataCountPerAngle); - double convertedAngleIfOver180 = convertAngleIfNecessary(estimatedPeak); - SpeedWithConfidence averagedSpeedWithConfidence = estimateSpeed(boatClass, windSpeed, convertedAngleIfOver180); - double originConfidence = averagedSpeedWithConfidence.getConfidence(); - double overallConfidence = (originConfidence + calcDataCountConfidence(dataCountPerAngle, estimatedPeak)) / 2; - Bearing bearing = new DegreeBearingImpl(convertedAngleIfOver180); - SpeedWithBearing speedWithBearing = new KnotSpeedWithBearingImpl(averagedSpeedWithConfidence.getObject().getKnots(), bearing); - return new SpeedWithBearingWithConfidenceImpl<>(speedWithBearing, overallConfidence, null); - } - - /** - * Uses the formula 1-​e^​((‑x)/​150) to create a confidence for the estimated peak. - * - * If the peak has 100 data points underlying, the confidence will be approx. 0.5 - * - * @param dataCountPerAngle - * @param estimatedPeak - * @return - */ - private double calcDataCountConfidence(int[] dataCountPerAngle, double estimatedPeak) { - int dataCount = dataCountPerAngle[(int) Math.round(estimatedPeak)]; - double confidence = 1 - Math.pow(Math.E, - dataCount / 150); - return confidence; - } - - private int[] getDataCountArray(BoatClass boatClass, Speed windSpeed, int startAngleInclusive, int endAngleExclusive) { - int[] dataCountPerAngle = polarDataService.getDataCountsForWindSpeed(boatClass, windSpeed, startAngleInclusive, endAngleExclusive); - return dataCountPerAngle; - } - - private double convertAngleIfNecessary(double estimatedPeak) { - double convertedAngleIfOver180 = estimatedPeak; - if (estimatedPeak > 180) { - convertedAngleIfOver180 = estimatedPeak - 360; - } - return convertedAngleIfOver180; - } - - private SpeedWithConfidence estimateSpeed(BoatClass boatClass, Speed windSpeed, double estimatedPeak) throws NotEnoughDataHasBeenAddedException { - SpeedWithConfidence speed = polarDataService.getSpeed(boatClass, windSpeed, new DegreeBearingImpl( - estimatedPeak)); - return speed; - } - - private double estimatePeak(BoatClass boatClass, Speed windSpeed, int startAngleInclusive, int endAngleExclusive, int[] dataCountPerAngle) - throws NotEnoughDataHasBeenAddedException { - // Find peak by averaging the angles that have at least 50% of the max datacount - int maxDataCount = Integer.MIN_VALUE; - for (int i = startAngleInclusive; i < endAngleExclusive; i++) { - if (dataCountPerAngle[i] > maxDataCount) { - maxDataCount = dataCountPerAngle[i]; - } - } - if (maxDataCount < MINIMUM_DATA_COUNT_FOR_ONE_ANGLE) { - // The angle with the most data points doesn't have sufficient data, for the polar data to have any - // significance. - throw new NotEnoughDataHasBeenAddedException("Only " + maxDataCount - + " points have been added for the angle with the most points. No significance."); - } - int sumOfAllUpperHalfAngles = 0; - int numberOfAnglesAdded = 0; - for (int i = startAngleInclusive; i < endAngleExclusive; i++) { - int dataCount = dataCountPerAngle[i]; - if (dataCount > maxDataCount / 2) { - sumOfAllUpperHalfAngles = sumOfAllUpperHalfAngles + i * dataCount; - numberOfAnglesAdded = numberOfAnglesAdded + dataCount; - } - } - double estimatedPeak = (double) sumOfAllUpperHalfAngles / (double) numberOfAnglesAdded; - return estimatedPeak; - } -} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java index 3c9528b8c76..01352002829 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java @@ -1,15 +1,15 @@ package com.sap.sailing.polars.impl; -import java.util.HashSet; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; +import org.apache.commons.math.analysis.polynomials.PolynomialFunction; + import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; import com.sap.sailing.domain.base.SpeedWithConfidence; -import com.sap.sailing.domain.base.impl.SpeedWithBearingWithConfidenceImpl; import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.LegType; import com.sap.sailing.domain.common.ManeuverType; @@ -17,8 +17,6 @@ import com.sap.sailing.domain.common.PolarSheetGenerationSettings; import com.sap.sailing.domain.common.PolarSheetsData; import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.Tack; -import com.sap.sailing.domain.common.impl.DegreeBearingImpl; -import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.polars.PolarDataService; @@ -43,9 +41,6 @@ public class PolarDataServiceImpl implements PolarDataService { private final PolarDataMiner polarDataMiner; - - - public PolarDataServiceImpl(Executor executor) { this.polarDataMiner = new PolarDataMiner(); } @@ -58,44 +53,13 @@ public class PolarDataServiceImpl implements PolarDataService { @Override public Set> getAverageTrueWindSpeedAndAngleCandidates(BoatClass boatClass, Speed speedOverGround, LegType legType, Tack tack) { - - // The following is an estimation function. It only serves as a stub. It's the same for all boatclasses and returns - // default maneuver angles. This is NOT final. - // The function is able to return boat speed values for windspeed alues between 5kn and 25kn , which are some kind of realistic - // for sailing boats. They are taken from the 505 polars we gathered in the races until now. - // TODO keep a cache of sampling points over which to fit a function for every boat class and then return the - // corresponding angle - - Set> resultSet = new HashSet<>(); - final int tackFactor = (tack.equals(Tack.PORT)) ? -1 : 1; - if (legType.equals(LegType.UPWIND)) { - CubicEquation upWindEquation = new CubicEquation(0.0002, -0.0245, 0.7602, -0.0463-speedOverGround.getKnots()); - int angle = 49 * tackFactor; - solveAndAddResults(resultSet, upWindEquation, angle); - } else if (legType.equals(LegType.DOWNWIND)) { - CubicEquation downWindEquation = new CubicEquation(0.0003, -0.0373, 1.5213, -2.1309-speedOverGround.getKnots()); - int angle = 30 * tackFactor; - solveAndAddResults(resultSet, downWindEquation, angle); - } - return resultSet; - //return polarDataMiner.estimateTrueWindSpeedAndAngleCandidates(boatClass, speedOverGround, legType, tack); - } - - private void solveAndAddResults(Set> result, CubicEquation equation, int angle) { - double[] windSpeedCandidates = equation.solve(); - for (int i = 0; i < windSpeedCandidates.length; i++) { - double windSpeedCandidateInKnots = windSpeedCandidates[i] > 0 ? windSpeedCandidates[i] : 0; - if (windSpeedCandidateInKnots < 40) { - result.add(new SpeedWithBearingWithConfidenceImpl(new KnotSpeedWithBearingImpl( - windSpeedCandidateInKnots, new DegreeBearingImpl(angle)), 0.00001, null)); - } - } + return polarDataMiner.estimateTrueWindSpeedAndAngleCandidates(boatClass, speedOverGround, legType, tack); } @Override public SpeedWithBearingWithConfidence getAverageSpeedWithBearing(BoatClass boatClass, - Speed windSpeed, LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException { - return polarDataMiner.getAverageSpeedAndCourseOverGround(boatClass, windSpeed, legType, tack); + Speed windSpeed, LegType legType, Tack tack, boolean useRegressionForSpeed) throws NotEnoughDataHasBeenAddedException { + return polarDataMiner.getAverageSpeedAndCourseOverGround(boatClass, windSpeed, legType, tack, useRegressionForSpeed); } @@ -170,4 +134,16 @@ public class PolarDataServiceImpl implements PolarDataService { } return closestTwsTwa; } + + @Override + public PolynomialFunction getSpeedRegressionFunction(BoatClass boatClass, LegType legType, Tack tack) + throws NotEnoughDataHasBeenAddedException { + return polarDataMiner.getSpeedRegressionFunction(boatClass, legType, tack); + } + + @Override + public PolynomialFunction getAngleRegressionFunction(BoatClass boatClass, LegType legType, Tack tack) + throws NotEnoughDataHasBeenAddedException { + return polarDataMiner.getAngleRegressionFunction(boatClass, legType, tack); + } } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java new file mode 100644 index 00000000000..d894bb15da1 --- /dev/null +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java @@ -0,0 +1,89 @@ +package com.sap.sailing.polars.mining; + +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.math.analysis.polynomials.PolynomialFunction; + +import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; +import com.sap.sailing.domain.base.impl.SpeedWithBearingWithConfidenceImpl; +import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.Speed; +import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.confidence.BearingWithConfidence; +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; +import com.sap.sailing.domain.tracking.WindWithConfidence; +import com.sap.sailing.polars.impl.CubicEquation; +import com.sap.sailing.polars.regression.IncrementalLeastSquares; +import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; +import com.sap.sailing.polars.regression.impl.IncrementalAnyOrderLeastSquaresImpl; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util.Pair; + +public class AngleAndSpeedRegression { + + private final IncrementalLeastSquares speedRegression = new IncrementalAnyOrderLeastSquaresImpl(3, false); + private final IncrementalLeastSquares angleRegression = new IncrementalAnyOrderLeastSquaresImpl(3); + + private double maxWindSpeedInKnots = -1; + + public void addData(WindWithConfidence> windSpeed, + BearingWithConfidence angleToTheWind, SpeedWithBearingWithConfidence boatSpeed) { + double windSpeedInKnots = windSpeed.getObject().getKnots(); + if (windSpeedInKnots > maxWindSpeedInKnots) { + maxWindSpeedInKnots = windSpeedInKnots; + } + speedRegression.addData(windSpeedInKnots, boatSpeed.getObject().getKnots()); + angleRegression.addData(windSpeedInKnots, angleToTheWind.getObject().getDegrees()); + } + + public SpeedWithBearingWithConfidence estimateSpeedAndAngle(Speed windSpeed) throws NotEnoughDataHasBeenAddedException { + double windSpeedInKnots = windSpeed.getKnots(); + if (windSpeedInKnots > maxWindSpeedInKnots) { + throw new NotEnoughDataHasBeenAddedException(); + } + double estimatedSpeed = speedRegression.getOrCreatePolynomialFunction().value(windSpeedInKnots); + double estimatedAngle = angleRegression.getOrCreatePolynomialFunction().value(windSpeedInKnots); + Bearing bearing = new DegreeBearingImpl(estimatedAngle); + SpeedWithBearing speedWithBearing = new KnotSpeedWithBearingImpl(estimatedSpeed, bearing); + return new SpeedWithBearingWithConfidenceImpl(speedWithBearing, /*FIXME*/ 0.5, null); + } + + public Set> estimateTrueWindSpeedAndAngleCandidates(Speed speedOverGround) throws NotEnoughDataHasBeenAddedException { + double[] coefficiants = speedRegression.getOrCreatePolynomialFunction().getCoefficients(); + CubicEquation equation = new CubicEquation(coefficiants[2], coefficiants[1], coefficiants[0], -speedOverGround.getKnots()); + + double[] windSpeedCandidates = equation.solve(); + Set> result = new HashSet<>(); + for (int i = 0; i < windSpeedCandidates.length; i++) { + double windSpeedCandidateInKnots = windSpeedCandidates[i]; + if (windSpeedCandidateInKnots >= 0 && windSpeedCandidateInKnots <= maxWindSpeedInKnots) { + double angle = 0; + boolean angleFound; + try { + angle = angleRegression.getOrCreatePolynomialFunction().value(windSpeedCandidateInKnots); + angleFound = true; + } catch(NotEnoughDataHasBeenAddedException e) { + angleFound = false; + } + if (angleFound) { + result.add(new SpeedWithBearingWithConfidenceImpl(new KnotSpeedWithBearingImpl( + windSpeedCandidateInKnots, new DegreeBearingImpl(angle)), 0.5 /*FIXME*/, null)); + } + } + } + + return result; + } + + public PolynomialFunction getSpeedRegressionFunction() throws NotEnoughDataHasBeenAddedException { + return speedRegression.getOrCreatePolynomialFunction(); + } + + public PolynomialFunction getAngleRegressionFunction() throws NotEnoughDataHasBeenAddedException { + return angleRegression.getOrCreatePolynomialFunction(); + } + +} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleClusterPolarClusterKey.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleClusterPolarClusterKey.java new file mode 100644 index 00000000000..48826f625bc --- /dev/null +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleClusterPolarClusterKey.java @@ -0,0 +1,12 @@ +package com.sap.sailing.polars.mining; + +import com.sap.sailing.domain.common.Bearing; +import com.sap.sse.datamining.data.Cluster; +import com.sap.sse.datamining.shared.annotations.Dimension; + +public interface AngleClusterPolarClusterKey extends BasePolarClusterKey { + + @Dimension(messageKey = "angleCluster") + Cluster getAngleCluster(); + +} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AverageAngleContainer.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AverageAngleContainer.java index 305fc51a59b..eef7022d7d5 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AverageAngleContainer.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AverageAngleContainer.java @@ -1,73 +1,19 @@ package com.sap.sailing.polars.mining; -import java.util.HashMap; -import java.util.Map; - -import com.sap.sailing.domain.base.BoatClass; -import com.sap.sailing.domain.common.Speed; -import com.sap.sse.datamining.data.Cluster; -import com.sap.sse.datamining.data.ClusterGroup; public class AverageAngleContainer { + private int dataCount = 0; + private double angleSumDeg = 0.0; + private double averageAngleDeg = 0.0; - private ClusterGroup speedClusterGroup; - - private static class DataCountAngleSumAndAverage { - private int dataCount; - private double angleSumDeg; - private double averageAngleDeg; - protected DataCountAngleSumAndAverage() { - super(); - this.dataCount = 0; - this.angleSumDeg = 0.0; - this.averageAngleDeg = 0.0; - } - public double getAverageAngleDeg() { - return averageAngleDeg; - } - public synchronized void add(double angleRad) { - dataCount++; - angleSumDeg += angleRad; - averageAngleDeg = angleSumDeg / (double) dataCount; - } - } - - private Map, DataCountAngleSumAndAverage>> dataCountAndAngleSumMap; - - public AverageAngleContainer(ClusterGroup speedClusterGroup) { - this.speedClusterGroup = speedClusterGroup; - dataCountAndAngleSumMap = new HashMap<>(); + public void addFix(double roundedAngleDeg) { + dataCount++; + angleSumDeg += roundedAngleDeg; + averageAngleDeg = angleSumDeg / (double) dataCount; } - public void addFix(BoatClass boatClass, Speed windSpeed, int roundedAngleDeg) { - Cluster windCluster = speedClusterGroup.getClusterFor(windSpeed); - if (!dataCountAndAngleSumMap.containsKey(boatClass)) { - dataCountAndAngleSumMap.put(boatClass, new HashMap<>()); - } - Map, DataCountAngleSumAndAverage> boatClassMap = dataCountAndAngleSumMap.get(boatClass); - DataCountAngleSumAndAverage triple = boatClassMap.get(windCluster); - if (triple == null) { - triple = new DataCountAngleSumAndAverage(); - boatClassMap.put(windCluster, triple); - } - triple.add(roundedAngleDeg); - } - - /** - * @return null if no entry exists for the windSpeed requested; a valid angle in degrees - * otherwise, representing the average angle of this cluster - */ - Double getAverageAngleDeg(BoatClass boatClass, Speed windSpeed) { - Double result = null; - if (dataCountAndAngleSumMap.containsKey(boatClass)) { - Map, DataCountAngleSumAndAverage> boatClassMap = dataCountAndAngleSumMap.get(boatClass); - Cluster windCluster = speedClusterGroup.getClusterFor(windSpeed); - if (boatClassMap.containsKey(windCluster)) { - DataCountAngleSumAndAverage triple = boatClassMap.get(windCluster); - result = triple.getAverageAngleDeg(); - } - } - return result; + Double getAverageAngleDeg() { + return averageAngleDeg; } } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/BasePolarClusterKey.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/BasePolarClusterKey.java new file mode 100644 index 00000000000..86f1fc2432b --- /dev/null +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/BasePolarClusterKey.java @@ -0,0 +1,11 @@ +package com.sap.sailing.polars.mining; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sse.datamining.shared.annotations.Dimension; + +public interface BasePolarClusterKey { + + @Dimension(messageKey = "boatClass") + BoatClass getBoatClass(); + +} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/BearingClusterGroup.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/BearingClusterGroup.java new file mode 100644 index 00000000000..f7f64ede35c --- /dev/null +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/BearingClusterGroup.java @@ -0,0 +1,54 @@ +package com.sap.sailing.polars.mining; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; + +import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sse.datamining.data.Cluster; +import com.sap.sse.datamining.data.ClusterBoundary; +import com.sap.sse.datamining.impl.data.ClusterWithLowerAndUpperBoundaries; +import com.sap.sse.datamining.impl.data.ComparatorClusterBoundary; +import com.sap.sse.datamining.impl.data.ComparisonStrategy; +import com.sap.sse.datamining.impl.data.FixClusterGroup; +import com.sap.sse.i18n.ResourceBundleStringMessages; + +public class BearingClusterGroup extends FixClusterGroup{ + + public BearingClusterGroup(int startAngle, int endAngle, int clusterSize) { + super("", createClusters(startAngle, endAngle, clusterSize)); + } + + private static Collection> createClusters(int startAngle, int endAngle, int clusterSize) { + int numberOfClusters = (endAngle - startAngle) / clusterSize; + List> clusters = new ArrayList>(); + for (int i = 0; i < numberOfClusters; i++) { + clusters.add(new ClusterWithLowerAndUpperBoundaries("", createBoundary(startAngle + i + * clusterSize, ComparisonStrategy.GREATER_THAN), createBoundary(startAngle + (i + 1) + * clusterSize, ComparisonStrategy.LOWER_EQUALS_THAN))); + } + return clusters; + } + + private static ClusterBoundary createBoundary(double angleInDeg, ComparisonStrategy strategy) { + Bearing angle = new DegreeBearingImpl(angleInDeg); + Comparator comparator = new Comparator() { + + @Override + public int compare(Bearing arg0, Bearing arg1) { + return new Double(arg0.getDegrees()).compareTo(new Double(arg1.getDegrees())); + } + }; + return new ComparatorClusterBoundary(comparator, angle, strategy); + } + + @Override + public String getLocalizedName(Locale locale, ResourceBundleStringMessages stringMessages) { + return ""; + } + + +} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicRegressionPerCourseProcessor.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicRegressionPerCourseProcessor.java new file mode 100644 index 00000000000..2b789fc8df2 --- /dev/null +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicRegressionPerCourseProcessor.java @@ -0,0 +1,161 @@ +package com.sap.sailing.polars.mining; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; + +import org.apache.commons.math.analysis.polynomials.PolynomialFunction; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; +import com.sap.sailing.domain.common.LegType; +import com.sap.sailing.domain.common.Speed; +import com.sap.sailing.domain.common.Tack; +import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; +import com.sap.sse.datamining.AdditionalResultDataBuilder; +import com.sap.sse.datamining.components.Processor; +import com.sap.sse.datamining.factories.GroupKeyFactory; +import com.sap.sse.datamining.impl.components.GroupedDataEntry; +import com.sap.sse.datamining.shared.GroupKey; + +public class CubicRegressionPerCourseProcessor implements Processor, Void> { + + private static final Logger logger = Logger.getLogger(CubicRegressionPerCourseProcessor.class.getName()); + + private final Map regressions = new HashMap<>(); + + @Override + public void processElement(GroupedDataEntry element) { + GroupKey key = element.getKey(); + AngleAndSpeedRegression regression; + synchronized (regressions) { + regression = regressions.get(key); + if (regression == null) { + regression = new AngleAndSpeedRegression(); + regressions.put(key, regression); + } + } + GPSFixMovingWithPolarContext fix = element.getDataEntry(); + regression.addData(fix.getWind(), fix.getAngleToTheWind(), fix.getBoatSpeed()); + } + + public SpeedWithBearingWithConfidence getAverageSpeedAndCourseOverGround(BoatClass boatClass, Speed windSpeed, LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException { + GroupKey key = createGroupKey(boatClass, legType, tack); + SpeedWithBearingWithConfidence estimatedSpeedAndAngle = null; + if (regressions.containsKey(key)) { + estimatedSpeedAndAngle = regressions.get(key).estimateSpeedAndAngle(windSpeed); + } else { + throw new NotEnoughDataHasBeenAddedException("Not enough data has been added to Per Course Regressions"); + } + return estimatedSpeedAndAngle; + } + + public Set> estimateTrueWindSpeedAndAngleCandidates(BoatClass boatClass, + Speed speedOverGround, LegType legType, Tack tack) { + GroupKey key = createGroupKey(boatClass, legType, tack); + Set> result = new HashSet<>(); + if (regressions.containsKey(key)) { + try { + result = regressions.get(key).estimateTrueWindSpeedAndAngleCandidates(speedOverGround); + } catch (NotEnoughDataHasBeenAddedException e) { + // Return empty result + } + } else { + // Return empty result; + } + return result; + } + + private GroupKey createGroupKey(final BoatClass boatClass, final LegType legType, + final Tack tack) { + TackAndLegTypePolarClusterKey key = new TackAndLegTypePolarClusterKey() { + + @Override + public BoatClass getBoatClass() { + return boatClass; + } + + @Override + public Tack getTack() { + return tack; + } + + @Override + public LegType getLegType() { + return legType; + } + }; + GroupKey compoundKey; + try { + compoundKey = GroupKeyFactory.createNestingCompoundKeyFor(key, PolarDataDimensionCollectionFactory + .getCubicRegressionPerCourseClusterKeyDimensions().iterator()); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + return compoundKey; + } + + public PolynomialFunction getSpeedRegressionFunction(BoatClass boatClass, LegType legType, Tack tack) + throws NotEnoughDataHasBeenAddedException { + GroupKey key = createGroupKey(boatClass, legType, tack); + PolynomialFunction polynomialFunction; + if (regressions.containsKey(key)) { + polynomialFunction = regressions.get(key).getSpeedRegressionFunction(); + } else { + throw new NotEnoughDataHasBeenAddedException(); + } + return polynomialFunction; + } + + public PolynomialFunction getAngleRegressionFunction(BoatClass boatClass, LegType legType, Tack tack) + throws NotEnoughDataHasBeenAddedException { + GroupKey key = createGroupKey(boatClass, legType, tack); + PolynomialFunction polynomialFunction; + if (regressions.containsKey(key)) { + polynomialFunction = regressions.get(key).getAngleRegressionFunction(); + } else { + throw new NotEnoughDataHasBeenAddedException(); + } + return polynomialFunction; + } + + @Override + public void onFailure(Throwable failure) { + logger.severe("Polar Data Mining Pipe failed."); + throw new RuntimeException("Polar Data Miner failed.", failure); + } + + + @Override + public Class> getInputType() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Class getResultType() { + // No result type here, since this is a special case of a processor. It's the end of the pipe so to say. + return null; + } + + + @Override + public void finish() throws InterruptedException { + // Nothing to do here + } + + @Override + public void abort() { + // TODO Auto-generated method stub + } + + @Override + public AdditionalResultDataBuilder getAdditionalResultData(AdditionalResultDataBuilder additionalDataBuilder) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/GPSFixMovingWithPolarContext.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/GPSFixMovingWithPolarContext.java index a6f89a5e5ec..4646958eb58 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/GPSFixMovingWithPolarContext.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/GPSFixMovingWithPolarContext.java @@ -7,9 +7,11 @@ import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.LegType; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.Tack; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.confidence.BearingWithConfidence; @@ -24,7 +26,7 @@ import com.sap.sse.common.Util.Pair; import com.sap.sse.datamining.data.Cluster; import com.sap.sse.datamining.data.ClusterGroup; -public class GPSFixMovingWithPolarContext implements PolarClusterKey { +public class GPSFixMovingWithPolarContext implements MovingAveragePolarClusterKey, AngleClusterPolarClusterKey { private final GPSFixMoving fix; private final TrackedRace race; @@ -32,15 +34,23 @@ public class GPSFixMovingWithPolarContext implements PolarClusterKey { private final ClusterGroup windSpeedClusterGroup; private final Set windSourcesToExcludeForBearing; private final Set windSourcesToExcludeForSpeed; + private final ClusterGroup angleClusterGroup; + private final BearingWithConfidence trueWindAngle; + private final WindWithConfidence> wind; + private final SpeedWithBearingWithConfidence boatSpeed; public GPSFixMovingWithPolarContext(GPSFixMoving fix, TrackedRace race, Competitor competitor, - ClusterGroup windSpeedClusterGroup) { + ClusterGroup windSpeedClusterGroup, ClusterGroup angleClusterGroup) { this.fix = fix; this.race = race; this.competitor = competitor; this.windSpeedClusterGroup = windSpeedClusterGroup; + this.angleClusterGroup = angleClusterGroup; this.windSourcesToExcludeForBearing = collectWindSourcesToIgnoreForBearing(); this.windSourcesToExcludeForSpeed = collectWindSourcesToIgnoreForSpeed(); + this.trueWindAngle = computeTrueWindAngle(); + this.wind = race.getWindWithConfidence(fix.getPosition(), fix.getTimePoint(), windSourcesToExcludeForSpeed); + this.boatSpeed = computeBoatSpeed(); } public GPSFixMoving getFix() { @@ -55,13 +65,11 @@ public class GPSFixMovingWithPolarContext implements PolarClusterKey { return competitor; } - @Override - public RoundedTrueWindAngle getRoundedTrueWindAngle() { - final BearingWithConfidence angleToTheWind = getAngleToTheWind(); - return angleToTheWind == null ? null : new RoundedTrueWindAngle(angleToTheWind.getObject()); + public BearingWithConfidence getAngleToTheWind() { + return trueWindAngle; } - public BearingWithConfidence getAngleToTheWind() { + private BearingWithConfidence computeTrueWindAngle() { SpeedWithBearing boatSpeed = race.getTrack(competitor).getEstimatedSpeed(fix.getTimePoint()); WindWithConfidence> wind = race.getWindWithConfidence(fix.getPosition(), fix.getTimePoint(), windSourcesToExcludeForBearing); @@ -76,16 +84,19 @@ public class GPSFixMovingWithPolarContext implements PolarClusterKey { @Override public Cluster getWindSpeedCluster() { - final WindWithConfidence> windWithConfidence = getWindSpeed(); + final WindWithConfidence> windWithConfidence = getWind(); return windWithConfidence == null ? null : windSpeedClusterGroup.getClusterFor(windWithConfidence.getObject()); } - public WindWithConfidence> getWindSpeed() { - WindWithConfidence> wind = race.getWindWithConfidence(fix.getPosition(), fix.getTimePoint(), windSourcesToExcludeForSpeed); + public WindWithConfidence> getWind() { return wind; } public SpeedWithBearingWithConfidence getBoatSpeed() { + return boatSpeed; + } + + private SpeedWithBearingWithConfidence computeBoatSpeed() { GPSFixTrack track = race.getTrack(competitor); return track.getEstimatedSpeed(fix.getTimePoint(), ConfidenceFactory.INSTANCE.createExponentialTimeDifferenceWeigher( // use a minimum confidence to avoid the bearing to flip to 270deg in case all is zero @@ -148,4 +159,34 @@ public class GPSFixMovingWithPolarContext implements PolarClusterKey { return race.getRace().getBoatClass(); } + @Override + public Tack getTack() { + return getTack(getAngleToTheWind().getObject()); + } + + public static Tack getTack(Bearing angleDifferenceWindToBoat) { + return angleDifferenceWindToBoat.getDegrees() <= 0 ? Tack.PORT : Tack.STARBOARD; + } + + @Override + public LegType getLegType() { + return getLegTypeWithPolarConstraints(getAngleToTheWind().getObject()); + } + + public static LegType getLegTypeWithPolarConstraints(Bearing angleDifferenceWindToBoat) { + double absoluteDiffInDeg = Math.abs(angleDifferenceWindToBoat.getDegrees()); + LegType result = null; + if (absoluteDiffInDeg > 20 && absoluteDiffInDeg < 75) { + result = LegType.UPWIND; + } else if (absoluteDiffInDeg > 105) { + result = LegType.DOWNWIND; + } + return result; + } + + @Override + public Cluster getAngleCluster() { + return angleClusterGroup.getClusterFor(getAngleToTheWind().getObject()); + } + } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/IncrementalRegressionProcessor.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/IncrementalRegressionProcessor.java deleted file mode 100755 index 054ad5b5842..00000000000 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/IncrementalRegressionProcessor.java +++ /dev/null @@ -1,252 +0,0 @@ -package com.sap.sailing.polars.mining; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.logging.Logger; - -import com.sap.sailing.domain.base.BoatClass; -import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; -import com.sap.sailing.domain.base.SpeedWithConfidence; -import com.sap.sailing.domain.base.impl.SpeedWithBearingWithConfidenceImpl; -import com.sap.sailing.domain.base.impl.SpeedWithConfidenceImpl; -import com.sap.sailing.domain.common.Bearing; -import com.sap.sailing.domain.common.LegType; -import com.sap.sailing.domain.common.Position; -import com.sap.sailing.domain.common.Speed; -import com.sap.sailing.domain.common.SpeedWithBearing; -import com.sap.sailing.domain.common.Tack; -import com.sap.sailing.domain.common.confidence.BearingWithConfidence; -import com.sap.sailing.domain.common.impl.DegreeBearingImpl; -import com.sap.sailing.domain.common.impl.KnotSpeedImpl; -import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; -import com.sap.sailing.domain.tracking.WindWithConfidence; -import com.sap.sailing.polars.analysis.PolarSheetAnalyzer; -import com.sap.sailing.polars.regression.BoatSpeedEstimator; -import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; -import com.sap.sailing.polars.regression.impl.WindSpeedAndAngleEstimator; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.Util.Pair; -import com.sap.sse.datamining.AdditionalResultDataBuilder; -import com.sap.sse.datamining.components.Processor; -import com.sap.sse.datamining.data.Cluster; -import com.sap.sse.datamining.data.ClusterGroup; -import com.sap.sse.datamining.factories.GroupKeyFactory; -import com.sap.sse.datamining.impl.components.GroupedDataEntry; -import com.sap.sse.datamining.shared.GroupKey; - -public class IncrementalRegressionProcessor implements Processor, Void>, - PolarSheetAnalyzer { - - private static final Logger logger = Logger.getLogger(IncrementalRegressionProcessor.class.getName()); - - private final Map boatSpeedEstimators = new HashMap(); - - private final ClusterGroup speedClusterGroup; - - private final Set availableBoatClasses = new HashSet<>(); - - private final Map, AverageAngleContainer> averageAngleContainers; - - public IncrementalRegressionProcessor(ClusterGroup speedClusterGroup) { - this.speedClusterGroup = speedClusterGroup; - averageAngleContainers = new HashMap<>(); - for (LegType legType : new LegType[] { LegType.UPWIND, LegType.DOWNWIND }) { - for (Tack tack : new Tack[] { Tack.PORT, Tack.STARBOARD }) { - averageAngleContainers.put(new Pair(legType, tack), - new AverageAngleContainer(speedClusterGroup)); - } - } - } - - @Override - public void processElement(GroupedDataEntry element) { - GroupKey key = element.getKey(); - BoatSpeedEstimator boatSpeedEstimator; - synchronized (boatSpeedEstimators) { - boatSpeedEstimator = boatSpeedEstimators.get(key); - if (boatSpeedEstimator == null) { - boatSpeedEstimator = new BoatSpeedEstimator(); - boatSpeedEstimators.put(key, boatSpeedEstimator); - availableBoatClasses.add(element.getDataEntry().getBoatClass()); - } - } - GPSFixMovingWithPolarContext fix = element.getDataEntry(); - BearingWithConfidence angleToTheWind = fix.getAngleToTheWind(); - WindWithConfidence> windSpeed = fix.getWindSpeed(); - SpeedWithBearingWithConfidence boatSpeedWithConfidence = fix.getBoatSpeed(); - // Only add GPS data if speeds and angles are not null, else do nothing! - if (angleToTheWind != null && windSpeed != null && boatSpeedWithConfidence != null) { - fillAverageAngleContainer(fix, element, windSpeed); - WindWithConfidence> windWithConfidenceForSpeed = windSpeed; - double confidenceForWindSpeed = windWithConfidenceForSpeed.getConfidence(); - double confidenceForWindBearing = angleToTheWind.getConfidence(); - double confidenceForBoatSpeed = boatSpeedWithConfidence.getConfidence(); - double averagedConfidence = (confidenceForBoatSpeed + confidenceForWindBearing + confidenceForWindSpeed) / 3; - boatSpeedEstimator.addData(boatSpeedWithConfidence.getObject(), averagedConfidence); - } - } - - private void fillAverageAngleContainer(GPSFixMovingWithPolarContext fix, - GroupedDataEntry element, - WindWithConfidence> windSpeed) { - final int roundedAngleDeg = fix.getRoundedTrueWindAngle().getAngleDeg(); - final LegType legType; - final Tack tack; - if (roundedAngleDeg < -90) { - legType = LegType.DOWNWIND; - tack = Tack.PORT; - } else if (roundedAngleDeg < 0) { - legType = LegType.UPWIND; - tack = Tack.PORT; - } else if (roundedAngleDeg > 90) { - legType = LegType.DOWNWIND; - tack = Tack.STARBOARD; - } else { // roundedAngleDeg > 0 && roundedAngleDeg <= 90 - legType = LegType.UPWIND; - tack = Tack.STARBOARD; - } - averageAngleContainers.get(new Pair<>(legType, tack)).addFix(element.getDataEntry().getBoatClass(), - windSpeed.getObject(), roundedAngleDeg); - } - - @Override - public void finish() throws InterruptedException { - // Nothing to do here - } - - @Override - public void abort() { - // TODO Auto-generated method stub - } - - @Override - public AdditionalResultDataBuilder getAdditionalResultData(AdditionalResultDataBuilder additionalDataBuilder) { - // TODO Auto-generated method stub - return null; - } - - public static class SpeedWithConfidenceAndDataCount { - private final SpeedWithConfidence speedWithConfidence; - private final int dataCount; - protected SpeedWithConfidenceAndDataCount(SpeedWithConfidence speedWithConfidence, int dataCount) { - super(); - this.speedWithConfidence = speedWithConfidence; - this.dataCount = dataCount; - } - public SpeedWithConfidence getSpeedWithConfidence() { - return speedWithConfidence; - } - public int getDataCount() { - return dataCount; - } - } - - SpeedWithConfidenceAndDataCount estimateBoatSpeed(final BoatClass boatClass, final Speed windSpeed, - final Bearing trueWindAngle) throws NotEnoughDataHasBeenAddedException { - PolarClusterKey key = new PolarClusterKey() { - @Override - public RoundedTrueWindAngle getRoundedTrueWindAngle() { - return new RoundedTrueWindAngle(trueWindAngle); - } - - @Override - public BoatClass getBoatClass() { - return boatClass; - } - - @Override - public Cluster getWindSpeedCluster() { - return speedClusterGroup.getClusterFor(windSpeed); - } - }; - GroupKey compoundKey; - try { - compoundKey = GroupKeyFactory.createNestingCompoundKeyFor(key, PolarDataDimensionCollectionFactory - .getClusterKeyDimensions().iterator()); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - throw new RuntimeException(e); - } - BoatSpeedEstimator boatSpeedEstimator = boatSpeedEstimators.get(compoundKey); - if (boatSpeedEstimator == null) { - throw new NotEnoughDataHasBeenAddedException(); - } - Speed speedWithoutConfidence = boatSpeedEstimator.estimateSpeed(windSpeed.getKnots(), trueWindAngle.getDegrees()); - final int dataCount = boatSpeedEstimator.getDataCount(); - double confidence = boatSpeedEstimator.getConfidence(); - return new SpeedWithConfidenceAndDataCount(new SpeedWithConfidenceImpl(speedWithoutConfidence, confidence, null), dataCount); - } - - Set> estimateTrueWindSpeedAndAngleCandidates(BoatClass boatClass, - Speed speedOverGround, LegType legType, Tack tack) { - List>> averageBoatSpeedAndCourseForWindSpeed = obtainPolarSamplingPoints( - boatClass, legType, tack); - - WindSpeedAndAngleEstimator windSpeedAndAngleEstimator = new WindSpeedAndAngleEstimator(averageBoatSpeedAndCourseForWindSpeed); - return windSpeedAndAngleEstimator.getAverageTrueWindSpeedAndAngleCandidates(speedOverGround); - } - - private List>> obtainPolarSamplingPoints(BoatClass boatClass, - LegType legType, Tack tack) { - List>> averageBoatSpeedAndCourseForWindSpeed = new ArrayList<>(); - for (int i = 0; i < 60; i++) { - Speed windSpeed = new KnotSpeedImpl(i * 0.5); - SpeedWithBearingWithConfidence averageSpeedAndCourseOverGround; - try { - averageSpeedAndCourseOverGround = getAverageSpeedAndCourseOverGround( - boatClass, windSpeed, legType, tack); - averageBoatSpeedAndCourseForWindSpeed.add(new Pair>(windSpeed, - averageSpeedAndCourseOverGround)); - } catch (NotEnoughDataHasBeenAddedException e) { - // ignore this, since there is not enough data for this windspeed. - } - } - return averageBoatSpeedAndCourseForWindSpeed; - } - - @Override - public void onFailure(Throwable failure) { - logger.severe("Polar Data Mining Pipe failed."); - throw new RuntimeException("Polar Data Miner failed.", failure); - } - - public Set getAvailableBoatClasses() { - return availableBoatClasses; - } - - @Override - public Class> getInputType() { - // TODO Auto-generated method stub - return null; - } - - @Override - public Class getResultType() { - // No result type here, since this is a special case of a processor. It's the end of the pipe so to say. - return null; - } - - @Override - public SpeedWithBearingWithConfidence getAverageSpeedAndCourseOverGround(BoatClass boatClass, - Speed windSpeed, LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException { - Double averageAngle = averageAngleContainers.get(new Pair<>(legType, tack)).getAverageAngleDeg( - boatClass, windSpeed); - return estimateSpeedForAverageAngle(boatClass, windSpeed, averageAngle); - } - - private SpeedWithBearingWithConfidence estimateSpeedForAverageAngle(BoatClass boatClass, Speed windSpeed, - Double averageAngle) throws NotEnoughDataHasBeenAddedException { - if (averageAngle == null) { - throw new NotEnoughDataHasBeenAddedException(); - } - DegreeBearingImpl angleToTheWind = new DegreeBearingImpl(averageAngle); - SpeedWithConfidence estimatedSpeed = estimateBoatSpeed(boatClass, windSpeed, angleToTheWind).getSpeedWithConfidence(); - SpeedWithBearing speedWithBearing = new KnotSpeedWithBearingImpl(estimatedSpeed.getObject().getKnots(), angleToTheWind); - return new SpeedWithBearingWithConfidenceImpl(speedWithBearing, estimatedSpeed.getConfidence(), null); - } - -} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarClusterKey.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/MovingAveragePolarClusterKey.java similarity index 50% rename from java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarClusterKey.java rename to java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/MovingAveragePolarClusterKey.java index 83b443ded5a..c54228800de 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarClusterKey.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/MovingAveragePolarClusterKey.java @@ -1,18 +1,10 @@ package com.sap.sailing.polars.mining; -import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.common.Speed; import com.sap.sse.datamining.data.Cluster; import com.sap.sse.datamining.shared.annotations.Dimension; -public interface PolarClusterKey { - - /** - * - * @return degrees boat to windDirection in DEG - */ - @Dimension(messageKey = "angleToWind") - RoundedTrueWindAngle getRoundedTrueWindAngle(); +public interface MovingAveragePolarClusterKey extends TackAndLegTypePolarClusterKey { /** * @@ -21,7 +13,4 @@ public interface PolarClusterKey { @Dimension(messageKey = "windSpeed") Cluster getWindSpeedCluster(); - @Dimension(messageKey = "boatClass") - BoatClass getBoatClass(); - } \ No newline at end of file diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/analysis/PolarSheetAnalyzer.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/MovingAverageProcessor.java similarity index 60% rename from java/com.sap.sailing.polars/src/com/sap/sailing/polars/analysis/PolarSheetAnalyzer.java rename to java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/MovingAverageProcessor.java index 07fe69692b2..c4f704620fc 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/analysis/PolarSheetAnalyzer.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/MovingAverageProcessor.java @@ -1,4 +1,6 @@ -package com.sap.sailing.polars.analysis; +package com.sap.sailing.polars.mining; + +import java.util.Set; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; @@ -6,8 +8,12 @@ import com.sap.sailing.domain.common.LegType; import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.Tack; import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; +import com.sap.sse.datamining.components.Processor; +import com.sap.sse.datamining.impl.components.GroupedDataEntry; -public interface PolarSheetAnalyzer { +public interface MovingAverageProcessor extends Processor, Void>{ SpeedWithBearingWithConfidence getAverageSpeedAndCourseOverGround(BoatClass boatClass, Speed windSpeed, LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException; + + Set getAvailableBoatClasses(); } \ No newline at end of file diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/MovingAverageProcessorImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/MovingAverageProcessorImpl.java new file mode 100755 index 00000000000..a7cf39d026b --- /dev/null +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/MovingAverageProcessorImpl.java @@ -0,0 +1,209 @@ +package com.sap.sailing.polars.mining; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; +import com.sap.sailing.domain.base.SpeedWithConfidence; +import com.sap.sailing.domain.base.impl.SpeedWithBearingWithConfidenceImpl; +import com.sap.sailing.domain.base.impl.SpeedWithConfidenceImpl; +import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.LegType; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.Speed; +import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.Tack; +import com.sap.sailing.domain.common.confidence.BearingWithConfidence; +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; +import com.sap.sailing.domain.tracking.WindWithConfidence; +import com.sap.sailing.polars.regression.MovingAverageBoatSpeedEstimator; +import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util.Pair; +import com.sap.sse.datamining.AdditionalResultDataBuilder; +import com.sap.sse.datamining.data.Cluster; +import com.sap.sse.datamining.data.ClusterGroup; +import com.sap.sse.datamining.factories.GroupKeyFactory; +import com.sap.sse.datamining.impl.components.GroupedDataEntry; +import com.sap.sse.datamining.shared.GroupKey; + +public class MovingAverageProcessorImpl implements MovingAverageProcessor { + + private static final Logger logger = Logger.getLogger(MovingAverageProcessorImpl.class.getName()); + + private final Map boatSpeedEstimators = new HashMap<>(); + + private final Map averageAngleContainers = new HashMap<>(); + + private final ClusterGroup speedClusterGroup; + + private final Set availableBoatClasses = new HashSet<>(); + + + + public MovingAverageProcessorImpl(ClusterGroup speedClusterGroup) { + this.speedClusterGroup = speedClusterGroup; + } + + @Override + public void processElement(GroupedDataEntry element) { + GroupKey key = element.getKey(); + MovingAverageBoatSpeedEstimator boatSpeedEstimator; + synchronized (boatSpeedEstimators) { + boatSpeedEstimator = boatSpeedEstimators.get(key); + if (boatSpeedEstimator == null) { + boatSpeedEstimator = new MovingAverageBoatSpeedEstimator(); + boatSpeedEstimators.put(key, boatSpeedEstimator); + } + } + AverageAngleContainer averageAngleContainer; + synchronized (averageAngleContainers) { + averageAngleContainer = averageAngleContainers.get(key); + if (averageAngleContainer == null) { + averageAngleContainer = new AverageAngleContainer(); + averageAngleContainers.put(key, averageAngleContainer); + } + } + GPSFixMovingWithPolarContext fix = element.getDataEntry(); + BearingWithConfidence angleToTheWind = fix.getAngleToTheWind(); + WindWithConfidence> windSpeed = fix.getWind(); + SpeedWithBearingWithConfidence boatSpeedWithConfidence = fix.getBoatSpeed(); + availableBoatClasses.add(element.getDataEntry().getBoatClass()); + WindWithConfidence> windWithConfidenceForSpeed = windSpeed; + double confidenceForWindSpeed = windWithConfidenceForSpeed.getConfidence(); + double confidenceForWindBearing = angleToTheWind.getConfidence(); + double confidenceForBoatSpeed = boatSpeedWithConfidence.getConfidence(); + double averagedConfidence = (confidenceForBoatSpeed + confidenceForWindBearing + confidenceForWindSpeed) / 3; + averageAngleContainer.addFix(angleToTheWind.getObject().getDegrees()); + boatSpeedEstimator.addData(boatSpeedWithConfidence.getObject(), averagedConfidence); + } + + @Override + public void finish() throws InterruptedException { + // Nothing to do here + } + + @Override + public void abort() { + // TODO Auto-generated method stub + } + + @Override + public AdditionalResultDataBuilder getAdditionalResultData(AdditionalResultDataBuilder additionalDataBuilder) { + // TODO Auto-generated method stub + return null; + } + + public static class SpeedWithConfidenceAndDataCount { + private final SpeedWithConfidence speedWithConfidence; + private final int dataCount; + protected SpeedWithConfidenceAndDataCount(SpeedWithConfidence speedWithConfidence, int dataCount) { + super(); + this.speedWithConfidence = speedWithConfidence; + this.dataCount = dataCount; + } + public SpeedWithConfidence getSpeedWithConfidence() { + return speedWithConfidence; + } + public int getDataCount() { + return dataCount; + } + } + + private SpeedWithConfidenceAndDataCount estimateBoatSpeed(GroupKey key) throws NotEnoughDataHasBeenAddedException { + MovingAverageBoatSpeedEstimator boatSpeedEstimator = boatSpeedEstimators.get(key); + if (boatSpeedEstimator == null) { + throw new NotEnoughDataHasBeenAddedException(); + } + Speed speedWithoutConfidence = boatSpeedEstimator.estimateSpeed(); + final int dataCount = boatSpeedEstimator.getDataCount(); + double confidence = boatSpeedEstimator.getConfidence(); + return new SpeedWithConfidenceAndDataCount(new SpeedWithConfidenceImpl(speedWithoutConfidence, confidence, null), dataCount); + } + + private Bearing estimateAngle(GroupKey key) throws NotEnoughDataHasBeenAddedException { + AverageAngleContainer averageAngleContainer = averageAngleContainers.get(key); + if (averageAngleContainer == null) { + throw new NotEnoughDataHasBeenAddedException(); + } + return new DegreeBearingImpl(averageAngleContainer.getAverageAngleDeg()); + } + + @Override + public void onFailure(Throwable failure) { + logger.severe("Polar Data Mining Pipe failed."); + throw new RuntimeException("Polar Data Miner failed.", failure); + } + + public Set getAvailableBoatClasses() { + return availableBoatClasses; + } + + @Override + public Class> getInputType() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Class getResultType() { + // No result type here, since this is a special case of a processor. It's the end of the pipe so to say. + return null; + } + + @Override + public SpeedWithBearingWithConfidence getAverageSpeedAndCourseOverGround(BoatClass boatClass, + Speed windSpeed, LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException { + SpeedWithBearingWithConfidence result = estimateSpeedAndAngle(boatClass, windSpeed, legType, tack); + return result; + } + + private SpeedWithBearingWithConfidence estimateSpeedAndAngle(BoatClass boatClass, Speed windSpeed, LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException { + GroupKey key = createGroupKey(boatClass, windSpeed, legType, tack); + Bearing angleDiffTrueWindToBoat = estimateAngle(key); + SpeedWithConfidence estimatedSpeed = estimateBoatSpeed(key).getSpeedWithConfidence(); + SpeedWithBearing speedWithBearing = new KnotSpeedWithBearingImpl(estimatedSpeed.getObject().getKnots(), angleDiffTrueWindToBoat); + return new SpeedWithBearingWithConfidenceImpl(speedWithBearing, estimatedSpeed.getConfidence(), null); + } + + private GroupKey createGroupKey(final BoatClass boatClass, final Speed windSpeed, final LegType legType, + final Tack tack) { + MovingAveragePolarClusterKey key = new MovingAveragePolarClusterKey() { + + @Override + public BoatClass getBoatClass() { + return boatClass; + } + + @Override + public Cluster getWindSpeedCluster() { + return speedClusterGroup.getClusterFor(windSpeed); + } + + @Override + public Tack getTack() { + return tack; + } + + @Override + public LegType getLegType() { + return legType; + } + }; + GroupKey compoundKey; + try { + compoundKey = GroupKeyFactory.createNestingCompoundKeyFor(key, PolarDataDimensionCollectionFactory + .getMovingAverageClusterKeyDimensions().iterator()); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + return compoundKey; + } + +} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataDimensionCollectionFactory.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataDimensionCollectionFactory.java index 14a342f1be7..19f4ef9b83e 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataDimensionCollectionFactory.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataDimensionCollectionFactory.java @@ -4,26 +4,69 @@ import java.util.ArrayList; import java.util.Collection; import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.LegType; +import com.sap.sailing.domain.common.Tack; +import com.sap.sse.datamining.data.Cluster; import com.sap.sse.datamining.factories.FunctionFactory; import com.sap.sse.datamining.functions.Function; public class PolarDataDimensionCollectionFactory { - public static Collection> getClusterKeyDimensions() throws NoSuchMethodException { + public static Collection> getMovingAverageClusterKeyDimensions() throws NoSuchMethodException { Collection> dimensions = new ArrayList<>(); FunctionFactory functionFactory = new FunctionFactory(); - Function angleFunction = functionFactory - .createMethodWrappingFunction(PolarClusterKey.class.getMethod("getRoundedTrueWindAngle", - new Class[0])); - Function windSpeedFunction = functionFactory.createMethodWrappingFunction(PolarClusterKey.class - .getMethod("getWindSpeedCluster", new Class[0])); - Function boatClassFunction = functionFactory.createMethodWrappingFunction(PolarClusterKey.class - .getMethod("getBoatClass", new Class[0])); + addTackAndLegTypeDimensions(dimensions, functionFactory); - dimensions.add(boatClassFunction); - dimensions.add(angleFunction); + addPolarBaseDimension(dimensions, functionFactory); + + + + Function windSpeedFunction = functionFactory.createMethodWrappingFunction(MovingAveragePolarClusterKey.class + .getMethod("getWindSpeedCluster", new Class[0])); dimensions.add(windSpeedFunction); return dimensions; } + private static void addTackAndLegTypeDimensions(Collection> dimensions, FunctionFactory functionFactory) + throws NoSuchMethodException { + Function tackFunction = functionFactory + .createMethodWrappingFunction(MovingAveragePolarClusterKey.class.getMethod("getTack", + new Class[0])); + Function legTypeFunction = functionFactory + .createMethodWrappingFunction(MovingAveragePolarClusterKey.class.getMethod("getLegType", + new Class[0])); + dimensions.add(tackFunction); + dimensions.add(legTypeFunction); + } + + private static void addPolarBaseDimension(Collection> dimensions, FunctionFactory functionFactory) + throws NoSuchMethodException { + Function boatClassFunction = functionFactory.createMethodWrappingFunction(MovingAveragePolarClusterKey.class + .getMethod("getBoatClass", new Class[0])); + dimensions.add(boatClassFunction); + } + + public static Collection> getCubicRegressionPerCourseClusterKeyDimensions() throws NoSuchMethodException { + Collection> dimensions = new ArrayList<>(); + FunctionFactory functionFactory = new FunctionFactory(); + addTackAndLegTypeDimensions(dimensions, functionFactory); + + addPolarBaseDimension(dimensions, functionFactory); + return dimensions; + } + + public static Collection> getSpeedRegressionPerAngleClusterClusterKeyDimensions() throws NoSuchMethodException { + Collection> dimensions = new ArrayList<>(); + FunctionFactory functionFactory = new FunctionFactory(); + + addPolarBaseDimension(dimensions, functionFactory); + + Function> angleDiffTrueWindToBoatFunction = functionFactory.createMethodWrappingFunction(AngleClusterPolarClusterKey.class + .getMethod("getAngleCluster", new Class[0])); + dimensions.add(angleDiffTrueWindToBoatFunction); + + return dimensions; + } + } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java index bfe976d024f..188b207983f 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java @@ -4,16 +4,20 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import org.apache.commons.math.analysis.polynomials.PolynomialFunction; + import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; import com.sap.sailing.domain.base.SpeedWithConfidence; +import com.sap.sailing.domain.base.impl.SpeedWithBearingWithConfidenceImpl; import com.sap.sailing.domain.base.impl.SpeedWithConfidenceImpl; import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.LegType; @@ -24,39 +28,57 @@ import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.Tack; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.KnotSpeedImpl; +import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.PolarSheetGenerationSettingsImpl; import com.sap.sailing.domain.common.impl.PolarSheetsDataImpl; import com.sap.sailing.domain.common.impl.PolarSheetsHistogramDataImpl; import com.sap.sailing.domain.common.impl.WindSpeedSteppingWithMaxDistance; import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.TrackedRace; -import com.sap.sailing.polars.analysis.PolarSheetAnalyzer; -import com.sap.sailing.polars.mining.IncrementalRegressionProcessor.SpeedWithConfidenceAndDataCount; +import com.sap.sailing.polars.impl.CubicEquation; import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; +import com.sap.sse.common.Util.Pair; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.data.ClusterGroup; import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.impl.components.GroupedDataEntry; import com.sap.sse.datamining.impl.components.ParallelFilteringProcessor; import com.sap.sse.datamining.impl.components.ParallelMultiDimensionsValueNestingGroupingProcessor; -public class PolarDataMiner implements PolarSheetAnalyzer { +public class PolarDataMiner { - private static final int THREAD_POOL_SIZE = Math.min(Runtime.getRuntime().availableProcessors(), 3); + private static final int THREAD_POOL_SIZE = Math.max((int) (Runtime.getRuntime().availableProcessors() * (3.0/4.0)), 3); private static final ThreadPoolExecutor executor = createExecutor(); private final PolarSheetGenerationSettings backendPolarSheetGenerationSettings; private static ThreadPoolExecutor createExecutor() { return new ThreadPoolExecutor(THREAD_POOL_SIZE, THREAD_POOL_SIZE, 60, TimeUnit.SECONDS, - new LinkedBlockingQueue()); + new LinkedBlockingQueue(100)); } private AbstractEnrichingProcessor enrichingProcessor; - private IncrementalRegressionProcessor incrementalRegressionProcessor; + + /** + * This processor keeps track of the moving average of the speed values and the average angle for each course (legtype tack combination) + */ + private MovingAverageProcessor movingAverageProcessor; + + /** + * This processor uses two cubic regressions angle to the true wind over windspeed and boatspeed over windspeed for each + * course (legtype tack combination) + */ + private CubicRegressionPerCourseProcessor cubicRegressionPerCourseProcessor; + + private SpeedRegressionPerAngleClusterProcessor speedRegressionPerAngleClusterProcessor; public PolarDataMiner() { - backendPolarSheetGenerationSettings = PolarSheetGenerationSettingsImpl.createBackendPolarSettings(); + this(PolarSheetGenerationSettingsImpl.createBackendPolarSettings()); + } + + public PolarDataMiner(PolarSheetGenerationSettings backendPolarSettings) { + backendPolarSheetGenerationSettings = backendPolarSettings; try { setUpWorkflow(); } catch (ClassCastException | NoSuchMethodException | SecurityException e) { @@ -70,18 +92,51 @@ public class PolarDataMiner implements PolarSheetAnalyzer { WindSpeedSteppingWithMaxDistance stepping = backendPolarSheetGenerationSettings.getWindSpeedStepping(); final ClusterGroup speedClusterGroup = SpeedClusterGroupFromWindSteppingCreator .createSpeedClusterGroupFrom(stepping); - incrementalRegressionProcessor = new IncrementalRegressionProcessor(speedClusterGroup); - Collection, ?>> grouperResultReceivers = new ArrayList, ?>>(); - grouperResultReceivers.add(incrementalRegressionProcessor); + movingAverageProcessor = new MovingAverageProcessorImpl(speedClusterGroup); + Collection, ?>> movingAverageGrouperResultReceivers = new ArrayList, ?>>(); + movingAverageGrouperResultReceivers.add(movingAverageProcessor); - Collection> dimensions = PolarDataDimensionCollectionFactory.getClusterKeyDimensions(); + Collection, ParameterProvider>> dimensionsWithParameterProviders = new ArrayList<>(); + for (Function function : PolarDataDimensionCollectionFactory.getMovingAverageClusterKeyDimensions()) { + dimensionsWithParameterProviders.add(new Pair, ParameterProvider>(function, ParameterProvider.NULL)); + } - Processor> groupingProcessor = new ParallelMultiDimensionsValueNestingGroupingProcessor( - GPSFixMovingWithPolarContext.class, executor, grouperResultReceivers, dimensions); + Processor> movingAverageGroupingProcessor = new ParallelMultiDimensionsValueNestingGroupingProcessor( + GPSFixMovingWithPolarContext.class, executor, movingAverageGrouperResultReceivers, dimensionsWithParameterProviders); + - Collection> filteringResultReceivers = Arrays.asList(groupingProcessor); + cubicRegressionPerCourseProcessor = new CubicRegressionPerCourseProcessor(); + Collection, ?>> regressionPerCourseGrouperResultReceivers = new ArrayList, ?>>(); + regressionPerCourseGrouperResultReceivers.add(cubicRegressionPerCourseProcessor); + + Collection, ParameterProvider>> dimensionsWithParameterProvidersForCubicRegression = new ArrayList<>(); + for (Function function : PolarDataDimensionCollectionFactory.getCubicRegressionPerCourseClusterKeyDimensions()) { + dimensionsWithParameterProvidersForCubicRegression.add(new Pair, ParameterProvider>(function, ParameterProvider.NULL)); + } + Processor> cubicRegressionPerCourseGroupingProcessor = new ParallelMultiDimensionsValueNestingGroupingProcessor( + GPSFixMovingWithPolarContext.class, executor, regressionPerCourseGrouperResultReceivers, dimensionsWithParameterProvidersForCubicRegression); + + ClusterGroup angleClusterGroup = createAngleClusterGroup(); + speedRegressionPerAngleClusterProcessor = new SpeedRegressionPerAngleClusterProcessor(angleClusterGroup); + Collection, ?>> regressionPerAngleClusterGrouperResultReceivers = new ArrayList, ?>>(); + regressionPerAngleClusterGrouperResultReceivers.add(speedRegressionPerAngleClusterProcessor); + + Collection, ParameterProvider>> dimensionsWithParameterProvidersForRegressionPerAngleCluster = new ArrayList<>(); + for (Function function : PolarDataDimensionCollectionFactory.getSpeedRegressionPerAngleClusterClusterKeyDimensions()) { + dimensionsWithParameterProvidersForRegressionPerAngleCluster.add(new Pair, ParameterProvider>(function, ParameterProvider.NULL)); + } + + Processor> regressionPerAngleClusterGroupingProcessor = new ParallelMultiDimensionsValueNestingGroupingProcessor( + GPSFixMovingWithPolarContext.class, executor, regressionPerAngleClusterGrouperResultReceivers, dimensionsWithParameterProvidersForRegressionPerAngleCluster); + + + Collection> filteringResultReceivers = new ArrayList<>(); + filteringResultReceivers.add(movingAverageGroupingProcessor); + filteringResultReceivers.add(cubicRegressionPerCourseGroupingProcessor); + filteringResultReceivers.add(regressionPerAngleClusterGroupingProcessor); + Processor filteringProcessor = new ParallelFilteringProcessor( - GPSFixMovingWithPolarContext.class, executor, filteringResultReceivers, new PolarFixFilterCriteria()); + GPSFixMovingWithPolarContext.class, executor, filteringResultReceivers, new PolarFixFilterCriteria(backendPolarSheetGenerationSettings.getNumberOfLeadingCompetitorsToInclude())); Collection> enrichingResultReceivers = Arrays .asList(filteringProcessor); @@ -93,12 +148,16 @@ public class PolarDataMiner implements PolarSheetAnalyzer { @Override protected GPSFixMovingWithPolarContext enrich(GPSFixMovingWithOriginInfo element) { return new GPSFixMovingWithPolarContext(element.getFix(), element.getTrackedRace(), element.getCompetitor(), - speedClusterGroup); + speedClusterGroup, angleClusterGroup); } }; } + private ClusterGroup createAngleClusterGroup() { + return new BearingClusterGroup(-180, 180, 5); + } + public void addFix(GPSFixMoving fix, Competitor competitor, TrackedRace trackedRace) { enrichingProcessor.processElement(new GPSFixMovingWithOriginInfo(fix, trackedRace, competitor)); @@ -120,12 +179,51 @@ public class PolarDataMiner implements PolarSheetAnalyzer { */ public SpeedWithConfidence estimateBoatSpeed(BoatClass boatClass, Speed windSpeed, Bearing trueWindAngle) throws NotEnoughDataHasBeenAddedException { - return incrementalRegressionProcessor.estimateBoatSpeed(boatClass, windSpeed, trueWindAngle).getSpeedWithConfidence(); + return speedRegressionPerAngleClusterProcessor.estimateBoatSpeed(boatClass, windSpeed, trueWindAngle); } public Set> estimateTrueWindSpeedAndAngleCandidates(BoatClass boatClass, Speed speedOverGround, LegType legType, Tack tack) { - return incrementalRegressionProcessor.estimateTrueWindSpeedAndAngleCandidates(boatClass, speedOverGround, legType, tack); + Set> resultSet = cubicRegressionPerCourseProcessor.estimateTrueWindSpeedAndAngleCandidates(boatClass, speedOverGround, legType, tack); + if (resultSet.isEmpty()) { + // FALLBACK function if no data was available + resultSet = getAverageTrueWindSpeedAndAngleCandidatesWithFallbackFunction(boatClass, speedOverGround, legType, tack); + } + return resultSet; + } + + private Set> getAverageTrueWindSpeedAndAngleCandidatesWithFallbackFunction(BoatClass boatClass, + Speed speedOverGround, LegType legType, Tack tack) { + + // The following is an estimation function. It only serves as a fallback. It's the same for all boatclasses and returns + // default maneuver angles. + // The function is able to return boat speed values for windspeed values between 5kn and 25kn , which are some kind of realistic + // for sailing boats. They are taken from the 505 polars we gathered in the races until now. + + Set> resultSet = new HashSet<>(); + final int tackFactor = (tack.equals(Tack.PORT)) ? -1 : 1; + if (legType.equals(LegType.UPWIND)) { + CubicEquation upWindEquation = new CubicEquation(0.0002, -0.0245, 0.7602, -0.0463-speedOverGround.getKnots()); + int angle = 49 * tackFactor; + solveAndAddResults(resultSet, upWindEquation, angle); + } else if (legType.equals(LegType.DOWNWIND)) { + CubicEquation downWindEquation = new CubicEquation(0.0003, -0.0373, 1.5213, -2.1309-speedOverGround.getKnots()); + int angle = 30 * tackFactor; + solveAndAddResults(resultSet, downWindEquation, angle); + } + return resultSet; + //return polarDataMiner.estimateTrueWindSpeedAndAngleCandidates(boatClass, speedOverGround, legType, tack); + } + + private void solveAndAddResults(Set> result, CubicEquation equation, int angle) { + double[] windSpeedCandidates = equation.solve(); + for (int i = 0; i < windSpeedCandidates.length; i++) { + double windSpeedCandidateInKnots = windSpeedCandidates[i] > 0 ? windSpeedCandidates[i] : 0; + if (windSpeedCandidateInKnots < 40) { + result.add(new SpeedWithBearingWithConfidenceImpl(new KnotSpeedWithBearingImpl( + windSpeedCandidateInKnots, new DegreeBearingImpl(angle)), 0.00001, null)); + } + } } public PolarSheetsData createFullSheetForBoatClass(BoatClass boatClass) { @@ -142,19 +240,18 @@ public class PolarDataMiner implements PolarSheetAnalyzer { Integer[] perAngle = new Integer[360]; Map perWindSpeed = new HashMap<>(); for (int angle = 0; angle < 360; angle++) { - SpeedWithConfidenceAndDataCount speedWithConfidenceAndDataCount; + SpeedWithConfidence speedWithConfidence; try { int convertedAngle = convertAngleIfNecessary(angle); - speedWithConfidenceAndDataCount = incrementalRegressionProcessor.estimateBoatSpeed(boatClass, + speedWithConfidence = speedRegressionPerAngleClusterProcessor.estimateBoatSpeed(boatClass, new KnotSpeedImpl(windSpeed), new DegreeBearingImpl(convertedAngle)); } catch (NotEnoughDataHasBeenAddedException e) { // No data so put in a 0 speed with 0 confidence - speedWithConfidenceAndDataCount = new SpeedWithConfidenceAndDataCount( - new SpeedWithConfidenceImpl(new KnotSpeedImpl(0), 0, null), 0); + speedWithConfidence = new SpeedWithConfidenceImpl(new KnotSpeedImpl(0), 0, null); } - averagedPolarDataByWindSpeed[windIndex][angle] = speedWithConfidenceAndDataCount.getSpeedWithConfidence().getObject().getKnots(); - int dataCount = speedWithConfidenceAndDataCount.getDataCount(); + averagedPolarDataByWindSpeed[windIndex][angle] = speedWithConfidence.getObject().getKnots(); + int dataCount = 200; /*FIXME*/ totalDataCount = totalDataCount + dataCount; // FIXME hard coded @@ -196,19 +293,14 @@ public class PolarDataMiner implements PolarSheetAnalyzer { } public Set getAvailableBoatClasses() { - return incrementalRegressionProcessor.getAvailableBoatClasses(); + return movingAverageProcessor.getAvailableBoatClasses(); } public int[] getDataCountsForWindSpeed(BoatClass boatClass, Speed windSpeed, int startAngleInclusive, int endAngleExclusive) { int[] dataCounts = new int[360]; for (int angle = 0; angle < 360; angle++) { if (angle >= startAngleInclusive && angle < endAngleExclusive) { - try { - dataCounts[angle] = incrementalRegressionProcessor.estimateBoatSpeed(boatClass, windSpeed, - new DegreeBearingImpl(convertAngleIfNecessary(angle))).getDataCount(); - } catch (NotEnoughDataHasBeenAddedException e) { - dataCounts[angle] = 0; - } + dataCounts[angle] = 0; /*FIXME*/ } else { dataCounts[angle] = -1; } @@ -216,9 +308,24 @@ public class PolarDataMiner implements PolarSheetAnalyzer { return dataCounts; } - @Override public SpeedWithBearingWithConfidence getAverageSpeedAndCourseOverGround(BoatClass boatClass, - Speed windSpeed, LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException { - return incrementalRegressionProcessor.getAverageSpeedAndCourseOverGround(boatClass, windSpeed, legType, tack); + Speed windSpeed, LegType legType, Tack tack, boolean useRegressionForSpeed) throws NotEnoughDataHasBeenAddedException { + SpeedWithBearingWithConfidence averageSpeedAndCourseOverGround = null; + if (useRegressionForSpeed) { + averageSpeedAndCourseOverGround = cubicRegressionPerCourseProcessor.getAverageSpeedAndCourseOverGround(boatClass, windSpeed, legType, tack); + } else { + averageSpeedAndCourseOverGround = movingAverageProcessor.getAverageSpeedAndCourseOverGround(boatClass, windSpeed, legType, tack); + } + return averageSpeedAndCourseOverGround; + } + + public PolynomialFunction getSpeedRegressionFunction(BoatClass boatClass, LegType legType, Tack tack) + throws NotEnoughDataHasBeenAddedException { + return cubicRegressionPerCourseProcessor.getSpeedRegressionFunction(boatClass, legType, tack); + } + + public PolynomialFunction getAngleRegressionFunction(BoatClass boatClass, LegType legType, Tack tack) + throws NotEnoughDataHasBeenAddedException { + return cubicRegressionPerCourseProcessor.getAngleRegressionFunction(boatClass, legType, tack); } } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarFixFilterCriteria.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarFixFilterCriteria.java index a6d5315122d..ecd1c2e610a 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarFixFilterCriteria.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarFixFilterCriteria.java @@ -1,22 +1,91 @@ package com.sap.sailing.polars.mining; +import java.util.Iterator; + import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Course; +import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.confidence.BearingWithConfidence; import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.GPSFixTrack; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.WindWithConfidence; import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util.Pair; import com.sap.sse.datamining.components.FilterCriterion; public class PolarFixFilterCriteria implements FilterCriterion { + + /** + * 0 if every competitor should be included. + * 1 if only the leading competitor should be included and so on. + */ + private final int numberOfLeadingCompetitorsToInclude; + + /** + * + * @param numberOfLeadingCompetitorsToInclude + * 0 if every competitor should be included.
    + * 1 if only the leading competitor should be included and so on. + */ + public PolarFixFilterCriteria(int numberOfLeadingCompetitorsToInclude) { + this.numberOfLeadingCompetitorsToInclude = numberOfLeadingCompetitorsToInclude; + } + + public PolarFixFilterCriteria() { + this.numberOfLeadingCompetitorsToInclude = 0; + } @Override public boolean matches(GPSFixMovingWithPolarContext element) { + boolean importantDataIsNotNull = importantDataIsNotNull(element); + if (!importantDataIsNotNull) { + return false; + } + boolean hasLegType = hasLegType(element); boolean afterStartTime = isAfterStartTime(element); boolean beforeFinishTime = isBeforeFinishTime(element); boolean noDirectionChange = !hasDirectionChange(element); - return (afterStartTime && beforeFinishTime && noDirectionChange); + boolean isInLeadingCompetitors = true; + if (numberOfLeadingCompetitorsToInclude > 0) { + isInLeadingCompetitors = isInLeadingCompetitors(element); + } + return (importantDataIsNotNull && hasLegType && afterStartTime && beforeFinishTime && noDirectionChange && isInLeadingCompetitors); + } + + private boolean importantDataIsNotNull(GPSFixMovingWithPolarContext element) { + BearingWithConfidence angleToTheWind = element.getAngleToTheWind(); + WindWithConfidence> windSpeed = element.getWind(); + SpeedWithBearingWithConfidence boatSpeedWithConfidence = element.getBoatSpeed(); + boolean result = false; + if (angleToTheWind != null && windSpeed != null && boatSpeedWithConfidence != null) { + result = true; + } + return result; + } + + private boolean hasLegType(GPSFixMovingWithPolarContext element) { + return element.getLegType() != null; + } + + private boolean isInLeadingCompetitors(GPSFixMovingWithPolarContext element) { + boolean result = false; + Iterator finishPassings = element.getRace() + .getMarkPassingsInOrder(element.getRace().getRace().getCourse().getLastWaypoint()).iterator(); + for (int i = 0; i < numberOfLeadingCompetitorsToInclude; i++) { + if (finishPassings.hasNext()) { + if (finishPassings.next().getCompetitor().equals(element.getCompetitor())) { + result = true; + break; + } + } else { + result = false; + break; + } + } + return result; } private boolean hasDirectionChange(GPSFixMovingWithPolarContext element) { diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/SpeedRegressionPerAngleClusterProcessor.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/SpeedRegressionPerAngleClusterProcessor.java new file mode 100644 index 00000000000..ce309df905e --- /dev/null +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/SpeedRegressionPerAngleClusterProcessor.java @@ -0,0 +1,135 @@ +package com.sap.sailing.polars.mining; + +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.SpeedWithConfidence; +import com.sap.sailing.domain.base.impl.SpeedWithConfidenceImpl; +import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.Speed; +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sailing.domain.common.impl.KnotSpeedImpl; +import com.sap.sailing.polars.regression.IncrementalLeastSquares; +import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; +import com.sap.sailing.polars.regression.impl.IncrementalAnyOrderLeastSquaresImpl; +import com.sap.sse.datamining.AdditionalResultDataBuilder; +import com.sap.sse.datamining.components.Processor; +import com.sap.sse.datamining.data.Cluster; +import com.sap.sse.datamining.data.ClusterGroup; +import com.sap.sse.datamining.factories.GroupKeyFactory; +import com.sap.sse.datamining.impl.components.GroupedDataEntry; +import com.sap.sse.datamining.shared.GroupKey; + +public class SpeedRegressionPerAngleClusterProcessor implements Processor, Void>{ + +private static final Logger logger = Logger.getLogger(CubicRegressionPerCourseProcessor.class.getName()); + + private final Map regressions = new HashMap<>(); + + private final ClusterGroup angleClusterGroup; + + public SpeedRegressionPerAngleClusterProcessor(ClusterGroup angleClusterGroup) { + this.angleClusterGroup = angleClusterGroup; + } + + @Override + public void processElement(GroupedDataEntry element) { + GroupKey key = element.getKey(); + IncrementalLeastSquares regression; + synchronized (regressions) { + regression = regressions.get(key); + if (regression == null) { + regression = new IncrementalAnyOrderLeastSquaresImpl(3, false); + regressions.put(key, regression); + } + } + GPSFixMovingWithPolarContext fix = element.getDataEntry(); + regression.addData(fix.getWind().getObject().getKnots(), fix.getBoatSpeed().getObject().getKnots()); + } + + /** + * There are angle clusters (size defined in the data mining pipeline construction), which each have their own + * regression for boatspeed over windspeed. + * We don't know the thresholds or centers of the angle clusters here, so we roughly interpolate by taking 10 + * values from angle-5 deg to angle+5 deg and average the speeds. + */ + public SpeedWithConfidence estimateBoatSpeed(BoatClass boatClass, Speed windSpeed, Bearing trueWindAngle) throws NotEnoughDataHasBeenAddedException { + double speedSum = 0; + double numberOfSpeeds = 0; + for (int i = -5; i <= 5; i++) { + GroupKey key = createGroupKey(boatClass, new DegreeBearingImpl(trueWindAngle.getDegrees() + i)); + if (regressions.containsKey(key)) { + speedSum += regressions.get(key).getOrCreatePolynomialFunction().value(windSpeed.getKnots()); + numberOfSpeeds++; + } else { + throw new NotEnoughDataHasBeenAddedException("Not enough data has been added to Per Course Regressions"); + } + } + Speed speed = new KnotSpeedImpl(speedSum / numberOfSpeeds); + return new SpeedWithConfidenceImpl(speed, /*FIXME*/ 0.5, null); + } + + private GroupKey createGroupKey(final BoatClass boatClass, final Bearing angle) { + AngleClusterPolarClusterKey key = new AngleClusterPolarClusterKey() { + + @Override + public BoatClass getBoatClass() { + return boatClass; + } + + @Override + public Cluster getAngleCluster() { + return angleClusterGroup.getClusterFor(angle); + } + + }; + GroupKey compoundKey; + try { + compoundKey = GroupKeyFactory.createNestingCompoundKeyFor(key, PolarDataDimensionCollectionFactory + .getSpeedRegressionPerAngleClusterClusterKeyDimensions().iterator()); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + return compoundKey; + } + + @Override + public void onFailure(Throwable failure) { + logger.severe("Polar Data Mining Pipe failed."); + throw new RuntimeException("Polar Data Miner failed.", failure); + } + + + @Override + public Class> getInputType() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Class getResultType() { + // No result type here, since this is a special case of a processor. It's the end of the pipe so to say. + return null; + } + + + @Override + public void finish() throws InterruptedException { + // Nothing to do here + } + + @Override + public void abort() { + // TODO Auto-generated method stub + } + + @Override + public AdditionalResultDataBuilder getAdditionalResultData(AdditionalResultDataBuilder additionalDataBuilder) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/TackAndLegTypePolarClusterKey.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/TackAndLegTypePolarClusterKey.java new file mode 100644 index 00000000000..c783abcc6bf --- /dev/null +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/TackAndLegTypePolarClusterKey.java @@ -0,0 +1,15 @@ +package com.sap.sailing.polars.mining; + +import com.sap.sailing.domain.common.LegType; +import com.sap.sailing.domain.common.Tack; +import com.sap.sse.datamining.shared.annotations.Dimension; + +public interface TackAndLegTypePolarClusterKey extends BasePolarClusterKey { + + @Dimension(messageKey = "tack") + Tack getTack(); + + @Dimension(messageKey = "legType") + LegType getLegType(); + +} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/IncrementalLeastSquares.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/IncrementalLeastSquares.java new file mode 100644 index 00000000000..57e3e4aca88 --- /dev/null +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/IncrementalLeastSquares.java @@ -0,0 +1,31 @@ +package com.sap.sailing.polars.regression; + +import org.apache.commons.math.analysis.polynomials.PolynomialFunction; + +/** + * Support incremental regression linear in one variable but potentially of higher order. + * + * @author Frederik Petersen (D054528) + * + */ +public interface IncrementalLeastSquares { + + /** + * Adds data to the least squares regression. This only updates the internal matrix and vector and will not trigger the + * actual least square solving. See {@link #getOrCreatePolynomialFunction()}. + * @param x + * @param y + */ + public abstract void addData(double x, double y); + + /** + * The polynomial function is cached and will be returned immediately if no data has been added since the last call. + * Otherwise the calculation is performed and the polynomial function is returned. It has a complexity of O(order) where + * order is the requested order of the polynomial. + * + * @return polynomial function estimating the data. Can be constant when only one data point has been added. + * @throws NotEnoughDataHasBeenAddedException Will be returned if no data has been added to the regression. + */ + public abstract PolynomialFunction getOrCreatePolynomialFunction() throws NotEnoughDataHasBeenAddedException; + +} \ No newline at end of file diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/IncrementalLinearRegressionProcessor.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/IncrementalLinearRegressionProcessor.java deleted file mode 100644 index d4babfb0573..00000000000 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/IncrementalLinearRegressionProcessor.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.sap.sailing.polars.regression; - -public interface IncrementalLinearRegressionProcessor { - - double getEstimatedY(double x) throws NotEnoughDataHasBeenAddedException; - - void addMeasuredPoint(double x, double y); - - double getSlope(); - - double getIntercept(); - - int getDataCount(); - - double getMeanOfY() throws NotEnoughDataHasBeenAddedException; - -} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/BoatSpeedEstimator.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/MovingAverageBoatSpeedEstimator.java similarity index 74% rename from java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/BoatSpeedEstimator.java rename to java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/MovingAverageBoatSpeedEstimator.java index 8c7e9203674..e9dc394912e 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/BoatSpeedEstimator.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/MovingAverageBoatSpeedEstimator.java @@ -5,7 +5,7 @@ import java.io.Serializable; import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.impl.KnotSpeedImpl; -import com.sap.sailing.polars.mining.IncrementalRegressionProcessor; +import com.sap.sailing.polars.mining.MovingAverageProcessorImpl; import com.sap.sse.concurrent.LockUtil; import com.sap.sse.concurrent.NamedReentrantReadWriteLock; @@ -17,13 +17,13 @@ import com.sap.sse.concurrent.NamedReentrantReadWriteLock; *

    * * Note that this class should only be used for a small wind interval on a polar sheet. The - * {@link IncrementalRegressionProcessor} is one example for that. It has one instance of the {@link BoatSpeedEstimator} + * {@link MovingAverageProcessorImpl} is one example for that. It has one instance of the {@link MovingAverageBoatSpeedEstimator} * for each wind boatclass, speed level and rounded angle combination. * * @author Frederik Petersen (D054528) * */ -public class BoatSpeedEstimator implements Serializable { +public class MovingAverageBoatSpeedEstimator implements Serializable { private static final long serialVersionUID = -254184914347332658L; @@ -35,10 +35,7 @@ public class BoatSpeedEstimator implements Serializable { private int dataCount = 0; - /** - * @param useLinearRegression if false mean of wind interval is used, otherwise linear regression - */ - public Speed estimateSpeed(double windSpeed, double angleToTheWind) throws NotEnoughDataHasBeenAddedException { + public Speed estimateSpeed() throws NotEnoughDataHasBeenAddedException { LockUtil.lockForRead(lock); if (dataCount < 1) { throw new NotEnoughDataHasBeenAddedException(); @@ -62,11 +59,21 @@ public class BoatSpeedEstimator implements Serializable { } public int getDataCount() { - return dataCount; + LockUtil.lockForRead(lock); + try { + return dataCount; + } finally { + LockUtil.unlockAfterRead(lock); + } } public double getConfidence() { - return confidenceSum / dataCount; + LockUtil.lockForRead(lock); + try { + return confidenceSum / dataCount; + } finally { + LockUtil.unlockAfterRead(lock); + } } private void readObject(ObjectInputStream ois) { diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/NotEnoughDataHasBeenAddedException.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/NotEnoughDataHasBeenAddedException.java index d15ad5ac066..6b8c54683b8 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/NotEnoughDataHasBeenAddedException.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/NotEnoughDataHasBeenAddedException.java @@ -1,5 +1,6 @@ package com.sap.sailing.polars.regression; + public class NotEnoughDataHasBeenAddedException extends Exception { private static final long serialVersionUID = 4134116458606258315L; @@ -12,4 +13,8 @@ public class NotEnoughDataHasBeenAddedException extends Exception { super(string); } + public NotEnoughDataHasBeenAddedException(String string, Throwable cause) { + super(string, cause); + } + } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalAnyOrderLeastSquaresImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalAnyOrderLeastSquaresImpl.java new file mode 100644 index 00000000000..6bf8e1b1e5f --- /dev/null +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalAnyOrderLeastSquaresImpl.java @@ -0,0 +1,300 @@ +package com.sap.sailing.polars.regression.impl; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.commons.math.analysis.polynomials.PolynomialFunction; +import org.apache.commons.math.linear.LUDecompositionImpl; +import org.apache.commons.math.linear.MatrixUtils; +import org.apache.commons.math.linear.MatrixVisitorException; +import org.apache.commons.math.linear.RealMatrix; +import org.apache.commons.math.linear.RealMatrixPreservingVisitor; +import org.apache.commons.math.linear.RealVector; +import org.apache.commons.math.linear.SingularMatrixException; + +import com.sap.sailing.polars.regression.IncrementalLeastSquares; +import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; +import com.sap.sse.concurrent.LockUtil; +import com.sap.sse.concurrent.NamedReentrantReadWriteLock; + +/** + * This class implements the incremental polynomial regression approach described here: + * + * + * http://erikerlandson.github.io/blog/2012/07/05/deriving-an-incremental-form-of-the-polynomial-regression-equations/ + * + * + * @author Frederik Petersen D054528 + * + */ +public class IncrementalAnyOrderLeastSquaresImpl implements IncrementalLeastSquares { + + private class NaNCheckerVisitor implements RealMatrixPreservingVisitor { + + private boolean hasNaNEntry = false; + + @Override + public void visit(int arg0, int arg1, double arg2) throws MatrixVisitorException { + if (Double.isNaN(arg2)) { + hasNaNEntry = true; + } + } + + @Override + public void start(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {} + + @Override + public double end() { + return 0.0; + } + + public boolean hasNaNEntry() { + return hasNaNEntry; + } + + } + + private double[][] matrixOfXSums; + + private double[] vectorOfXYMultSums; + + private int polynomialOrder; + + private AtomicLong numberOfPointsAdded = new AtomicLong(0); + + private AtomicBoolean functionNeedsUpdate = new AtomicBoolean(true); + + private final NamedReentrantReadWriteLock lock = new NamedReentrantReadWriteLock("IncrementalLeastSquaresLock", + false); + + private final NamedReentrantReadWriteLock cacheLock = new NamedReentrantReadWriteLock("IncrementalLeastSquaresCacheLock", + true); + + private PolynomialFunction cachedFunction; + + private final boolean hasIntercept; + private final boolean useSymbollicInversionIfPossible; + + public IncrementalAnyOrderLeastSquaresImpl(int polynomialOrder, boolean hasIntercept, boolean useSymbollicInversionIfPossible) { + this.hasIntercept = hasIntercept; + this.polynomialOrder = polynomialOrder; + this.useSymbollicInversionIfPossible = useSymbollicInversionIfPossible; + if (hasIntercept) { + matrixOfXSums = new double[polynomialOrder + 1][polynomialOrder + 1]; + vectorOfXYMultSums = new double[polynomialOrder + 1]; + } else { + matrixOfXSums = new double[polynomialOrder][polynomialOrder]; + vectorOfXYMultSums = new double[polynomialOrder]; + } + } + + public IncrementalAnyOrderLeastSquaresImpl(int polynomialOrder) { + this(polynomialOrder, true, true); + } + + public IncrementalAnyOrderLeastSquaresImpl(int polynomialOrder, boolean hasIntercept) { + this(polynomialOrder, hasIntercept, true); + } + + @Override + public void addData(double x, double y) { + LockUtil.lockForWrite(lock); + try { + numberOfPointsAdded.incrementAndGet(); + functionNeedsUpdate.set(true); + for (int i = 0; hasIntercept ? i <= polynomialOrder : i < polynomialOrder; i++) { + int powerI = hasIntercept ? i : i+1; + vectorOfXYMultSums[i] += y * Math.pow(x, powerI); + for (int j = 0; hasIntercept ? j <= polynomialOrder : j < polynomialOrder; j++) { + int powerJ = hasIntercept ? j : j+1; + if (powerI == 0 && powerJ == 0) { + matrixOfXSums[i][j] += 1; + } else { + matrixOfXSums[i][j] += Math.pow(x, powerI+powerJ); + } + } + } + } finally { + LockUtil.unlockAfterWrite(lock); + } + } + + @Override + public PolynomialFunction getOrCreatePolynomialFunction() throws NotEnoughDataHasBeenAddedException { + + PolynomialFunction resultFunction; + if (!functionNeedsUpdate.get()) { + LockUtil.lockForRead(cacheLock); + try { + resultFunction = new PolynomialFunction(cachedFunction.getCoefficients()); + } finally { + LockUtil.unlockAfterRead(cacheLock); + } + } else { + if (numberOfPointsAdded.get() < 1) { + throw new NotEnoughDataHasBeenAddedException("No points have been added to the regression yet."); + } + if (numberOfPointsAdded.get() == 1) { + resultFunction = createConstantFunctionForOneDataPoint(); + } else { + resultFunction = createEstimatingPolynomialFunction(); + } + LockUtil.lockForWrite(cacheLock); + try { + cachedFunction = new PolynomialFunction(resultFunction.getCoefficients()); + } finally { + LockUtil.unlockAfterWrite(cacheLock); + } + functionNeedsUpdate.set(false); + } + return resultFunction; + } + + private PolynomialFunction createEstimatingPolynomialFunction() throws NotEnoughDataHasBeenAddedException { + PolynomialFunction resultFunction; + RealMatrix matrixOfXSumsCopy; + RealVector vectorOfXYMultSumsCopy; + LockUtil.lockForRead(lock); + try { + matrixOfXSumsCopy = MatrixUtils.createRealMatrix(matrixOfXSums); + vectorOfXYMultSumsCopy = MatrixUtils.createRealVector(vectorOfXYMultSums); + } finally { + LockUtil.unlockAfterRead(lock); + } + LeastSquaresInversionHelperType type = LeastSquaresInversionHelperType.getType(polynomialOrder, hasIntercept); + RealMatrix inversedMatrix = null; + if (useSymbollicInversionIfPossible && type != null) { + inversedMatrix = type.inverseMatrix(matrixOfXSumsCopy); + } + if (inversedMatrix == null) { + try { + inversedMatrix = new LUDecompositionImpl(matrixOfXSumsCopy).getSolver().getInverse(); + } catch (SingularMatrixException singularMatrixException) { + throw new NotEnoughDataHasBeenAddedException("Matrix singular, all x input equal?", singularMatrixException); + } + } else { + NaNCheckerVisitor nanChecker = new NaNCheckerVisitor(); + inversedMatrix.walkInOptimizedOrder(nanChecker); + if (nanChecker.hasNaNEntry()) { + throw new NotEnoughDataHasBeenAddedException("Matrix singular, all x input equal?"); + } + + } + RealVector coeffs = inversedMatrix.operate(vectorOfXYMultSumsCopy); + if (!hasIntercept) { + resultFunction = createFunctionWithZeroIntercept(coeffs); + } else { + resultFunction = new PolynomialFunction(coeffs.toArray()); + } + return resultFunction; + } + + private PolynomialFunction createFunctionWithZeroIntercept(RealVector coeffs) { + PolynomialFunction resultFunction; + double[] coeffsNoIntercept = coeffs.toArray(); + double[] coeffsWithZeroIntercept = new double[coeffsNoIntercept.length + 1]; + coeffsWithZeroIntercept[0] = 0; + for (int i = 1; i < coeffsWithZeroIntercept.length; i++) { + coeffsWithZeroIntercept[i] = coeffsNoIntercept[i-1]; + } + resultFunction = new PolynomialFunction(coeffsWithZeroIntercept); + return resultFunction; + } + + private PolynomialFunction createConstantFunctionForOneDataPoint() { + LockUtil.lockForRead(lock); + double[] coeff = new double[1]; + try { + coeff[0] = vectorOfXYMultSums[0]; + } finally { + LockUtil.unlockAfterRead(lock); + } + return new PolynomialFunction(coeff); + } + + private enum LeastSquaresInversionHelperType { + + CUBIC, + CUBIC_NO_INTERCEPT; + + + /** + * + * @param realMatrix matrix to inverse + * @return Inversed matrix if symbolic inversion was found. Else null + */ + private RealMatrix inverseMatrix(RealMatrix realMatrix) { + RealMatrix result = null; + switch (this) { + case CUBIC_NO_INTERCEPT: + double[][] originDataCNI = realMatrix.getData(); + double a = originDataCNI[0][0]; + double b = originDataCNI[0][1]; + double c = originDataCNI[0][2]; + double d = originDataCNI[1][2]; + double e = originDataCNI[2][2]; + double[][] matrixDataCNI = { { d * d - c * e, b * e - c * d, c * c - b * d }, + { b * e - c * d, c * c - a * e, a * d - b * c }, + { c * c - b * d, a * d - b * c, b * b - a * c } }; + RealMatrix intermediateMatrixCNI = MatrixUtils.createRealMatrix(matrixDataCNI); + double factorCNI = (1.0 / (-a*c*e + a*d*d + b*b*e-2*b*c*d+c*c*c)); + result = intermediateMatrixCNI.scalarMultiply(factorCNI); + break; + case CUBIC: + double[][] originDataC = realMatrix.getData(); + double n = originDataC[0][0]; + a = originDataC[0][1]; + b = originDataC[0][2]; + c = originDataC[0][3]; + d = originDataC[1][3]; + e = originDataC[2][3]; + double f = originDataC[3][3]; + double[][] matrixDataC = { + { -d * d * d + 2 * c * e * d + b * f * d - b * e * e - c * c * f, + -e * c * c + d * d * c + b * f * c + a * e * e - b * d * e - a * d * f, + -f * b * b + d * d * b + c * e * b - c * c * d - a * d * e + a * c * f, + c * c * c - 2 * b * d * c - a * e * c + a * d * d + b * b * e }, + { -e * c * c + d * d * c + b * f * c + a * e * e - b * d * e - a * d * f, + -f * b * b + 2 * c * e * b - c * c * d - e * e * n + d * f * n, + c * c * c - b * d * c - a * e * c - f * n * c + a * b * f + d * e * n, + d * b * b - c * c * b - a * e * b + a * c * d - d * d * n + c * e * n }, + { -f * b * b + d * d * b + c * e * b - c * c * d - a * d * e + a * c * f, + c * c * c - b * d * c - a * e * c - f * n * c + a * b * f + d * e * n, + -f * a * a + 2 * c * d * a - b * c * c - d * d * n + b * f * n, + e * a * a - c * c * a - b * d * a + b * b * c + c * d * n - b * e * n }, + { c * c * c - 2 * b * d * c - a * e * c + a * d * d + b * b * e, + d * b * b - c * c * b - a * e * b + a * c * d - d * d * n + c * e * n, + e * a * a - c * c * a - b * d * a + b * b * c + c * d * n - b * e * n, + -b * b * b + 2 * a * c * b + d * n * b - a * a * d - c * c * n } }; + RealMatrix intermediateMatrixC = MatrixUtils.createRealMatrix(matrixDataC); + double factorC = 1.0 / (-a * a * d * f + a * a * e * e + 2 * c + * (a * (b * f + d * d) + e * (b * b + d * n)) - c * c * (2 * a * e + 3 * b * d + f * n) + b + * (-2 * a * d * e + d * f * n + e * e * -n) - b * b * b * f + b * b * d * d + c * c * c * c - d + * d * d * n); + result = intermediateMatrixC.scalarMultiply(factorC); + break; + default: + break; + } + return result; + } + + /** + * + * @param order Order of the polynomial + * @param hasIntercept If the least squares is set to no intercept + * @return type of the polynomial or null if it doesn't exist in this implementation + */ + private static LeastSquaresInversionHelperType getType(int order, boolean hasIntercept) { + LeastSquaresInversionHelperType result = null; + switch (order) { + case 3: + result = hasIntercept ? CUBIC : CUBIC_NO_INTERCEPT; + break; + } + return result; + } + + } + +} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalLeastSquaresProcessor.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalLeastSquaresProcessor.java deleted file mode 100644 index 64b99193c44..00000000000 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalLeastSquaresProcessor.java +++ /dev/null @@ -1,130 +0,0 @@ -package com.sap.sailing.polars.regression.impl; - -import com.sap.sailing.polars.regression.IncrementalLinearRegressionProcessor; -import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; - -/** - * Allows a linear regression of a 2 dimensional and growing dataset. It has a complexity of O(1) - * - * The algorithm and variable naming is similar to the german wikipedia - * article - * - * The difference is that some parts of the equation are memorized as fields and updated incrementally. - * - * @author Frederik Petersen (D054528) - * - */ -public class IncrementalLeastSquaresProcessor implements IncrementalLinearRegressionProcessor { - - private double alpha1 = 0; - - private double alpha0 = 0; - - private double partA = 0; - - private double partB = 0; - - private double meanOfX = 0; - - private double meanOfY = 0; - - private int numberOfPointsAdded = 0; - - @Override - public double getEstimatedY(double x) throws NotEnoughDataHasBeenAddedException { - if (numberOfPointsAdded < 1) { - throw new NotEnoughDataHasBeenAddedException(); - } - - double result; - if (numberOfPointsAdded < 2) { - // Return the y value of the only added data point. - result = meanOfY; - } else { - result = alpha0 + alpha1 * x; - } - - return result; - } - - @Override - public void addMeasuredPoint(double x, double y) { - numberOfPointsAdded++; - - calculatePartA(x, y); - calculatePartB(x, y); - - addToArithmeticMeanOfX(x); - addToArithmeticMeanOfY(y); - - calculateFactor1(); - calculateFactor2(); - } - - private void calculateFactor2() { - alpha0 = meanOfY - alpha1 * meanOfX; - } - - private void calculateFactor1() { - double numerator = calculateNumeratorOfFactor1(); - double denominator = calculateDenominatorOfFactor1(); - if (denominator == 0) { - alpha1 = 0; - } else { - alpha1 = numerator / denominator; - } - } - - private double calculateNumeratorOfFactor1() { - return partA - numberOfPointsAdded * meanOfX * meanOfY; - } - - private double calculateDenominatorOfFactor1() { - return partB - numberOfPointsAdded * meanOfX * meanOfX; - } - - private void addToArithmeticMeanOfY(double y) { - meanOfY = addToMean(y, meanOfY); - } - - private void addToArithmeticMeanOfX(double x) { - meanOfX = addToMean(x, meanOfX); - } - - private double addToMean(double z, double oldMean) { - return oldMean + (z - oldMean) / numberOfPointsAdded; - } - - private void calculatePartB(double x, double y) { - partB = partB + x * x; - } - - private void calculatePartA(double x, double y) { - partA = partA + x * y; - } - - @Override - public double getSlope() { - return alpha1; - } - - @Override - public double getIntercept() { - return alpha0; - } - - @Override - public int getDataCount() { - return numberOfPointsAdded; - } - - @Override - public double getMeanOfY() throws NotEnoughDataHasBeenAddedException { - if (numberOfPointsAdded < 1) { - throw new NotEnoughDataHasBeenAddedException(); - } - return meanOfY; - } - -} diff --git a/java/com.sap.sailing.selenium.test/ci-test-environment.xml b/java/com.sap.sailing.selenium.test/ci-test-environment.xml index 8dc260e081a..048f5f00236 100755 --- a/java/com.sap.sailing.selenium.test/ci-test-environment.xml +++ b/java/com.sap.sailing.selenium.test/ci-test-environment.xml @@ -13,7 +13,7 @@ - http://riccardo:riccardo@localhost:8085/ + http://localhost:8085/ - http://riccardo:riccardo@localhost:8888/ + http://localhost:8888/ ./bin/surefire-reports/ diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/AbstractSeleniumTest.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/AbstractSeleniumTest.java index acaaa4e2892..40bdfbf8248 100755 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/AbstractSeleniumTest.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/AbstractSeleniumTest.java @@ -114,12 +114,13 @@ public abstract class AbstractSeleniumTest { Cookie result = null; URL url = new URL(contextRoot + LOGIN_URL); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setInstanceFollowRedirects(false); connection.setRequestMethod("POST"); connection.setDoOutput(true); connection.connect(); connection.getOutputStream().write("username=admin&password=admin".getBytes()); - if (connection.getResponseCode() != 200) { - throw new RuntimeException(connection.getResponseMessage()); + if (connection.getResponseCode() / 100 != 3) { // expecting something like "302 Found" which redirects to the success page + throw new RuntimeException("" + connection.getResponseCode() + " "+connection.getResponseMessage()); } List cookies = connection.getHeaderFields().get("Set-Cookie"); if (cookies != null) { diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java index 2e088d57af2..e138920caad 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java @@ -84,8 +84,7 @@ public class TestLeaderboardConfiguration extends AbstractSeleniumTest { List races = table.getRaceNames(); assertThat(races.size(), equalTo(5)); - assertThat(table.getEntries().size(), equalTo(0)); - + assertThat(table.getEntries().size(), equalTo(28)); // the regatta already has the races linked; regatta leaderboard obtains competitors from regatta adminConsoleWindow.switchToWindow(); AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); LeaderboardConfigurationPanelPO leaderboardConfiguration = adminConsole.goToLeaderboardConfiguration(); diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/CompetitorJsonDeserializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/CompetitorJsonDeserializer.java index dcd7490de68..84c7591ac7e 100644 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/CompetitorJsonDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/CompetitorJsonDeserializer.java @@ -51,6 +51,8 @@ public class CompetitorJsonDeserializer implements JsonDeserializer } String name = (String) object.get(CompetitorJsonSerializer.FIELD_NAME); String displayColorAsString = (String) object.get(CompetitorJsonSerializer.FIELD_DISPLAY_COLOR); + String email = (String) object.get(CompetitorJsonSerializer.FIELD_EMAIL); + final Color displayColor; if (displayColorAsString == null || displayColorAsString.isEmpty()) { displayColor = null; @@ -67,7 +69,8 @@ public class CompetitorJsonDeserializer implements JsonDeserializer boat = boatJsonDeserializer.deserialize(Helpers.getNestedObjectSafe(object, CompetitorJsonSerializer.FIELD_BOAT)); } - Competitor competitor = competitorStore.getOrCreateCompetitor(competitorId, name, displayColor, team, boat); + + Competitor competitor = competitorStore.getOrCreateCompetitor(competitorId, name, displayColor, email, team, boat); return competitor; } catch (Exception e) { throw new JsonDeserializationException(e); diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/CompetitorJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/CompetitorJsonSerializer.java index a27faec44a9..04dd097e1c5 100644 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/CompetitorJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/CompetitorJsonSerializer.java @@ -31,6 +31,7 @@ public class CompetitorJsonSerializer implements JsonSerializer { public static final String FIELD_TEAM = "team"; public static final String FIELD_BOAT = "boat"; public static final String FIELD_DISPLAY_COLOR = "displayColor"; + public static final String FIELD_EMAIL = "email"; private final JsonSerializer teamJsonSerializer; private final JsonSerializer boatJsonSerializer; @@ -68,6 +69,7 @@ public class CompetitorJsonSerializer implements JsonSerializer { } result.put(FIELD_NAME, competitor.getName()); result.put(FIELD_DISPLAY_COLOR, competitor.getColor() == null ? null : competitor.getColor().getAsHtml()); + result.put(FIELD_EMAIL, competitor.getEmail()); result.put(FIELD_SAILID, competitor.getBoat() == null ? "" : competitor.getBoat().getSailID()); final Nationality nationality = competitor.getTeam() == null ? null : competitor.getTeam().getNationality(); result.put(FIELD_NATIONALITY, nationality == null ? "" : nationality.getThreeLetterIOCAcronym()); diff --git a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/racelog/RaceLogEventSerializerTest.java b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/racelog/RaceLogEventSerializerTest.java index 0e35f55d5bc..1d3f145cbdf 100644 --- a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/racelog/RaceLogEventSerializerTest.java +++ b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/racelog/RaceLogEventSerializerTest.java @@ -237,7 +237,7 @@ public class RaceLogEventSerializerTest { public void testRegisterCompetitorEventSerializer() { // we use the real event type here because we do not want to re-implement the dispatching. RaceLogEvent event = factory.createRegisterCompetitorEvent(null, author, 0, - DomainFactory.INSTANCE.getOrCreateCompetitor("comp", "comp", null, null, null)); + DomainFactory.INSTANCE.getOrCreateCompetitor("comp", "comp", null, null, null, null)); serializer.serialize(event); verify(registerCompetitorEventSerializer).serialize(event); } diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/RegattaJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/RegattaJsonSerializer.java index 9cb04846da0..b74ad82272b 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/RegattaJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/RegattaJsonSerializer.java @@ -49,7 +49,7 @@ public class RegattaJsonSerializer implements JsonSerializer { if(competitorSerializer != null) { JSONArray competitorsJson = new JSONArray(); - for (Competitor competitor: regatta.getCompetitors()) { + for (Competitor competitor: regatta.getAllCompetitors()) { competitorsJson.add(competitorSerializer.serialize(competitor)); } result.put(FIELD_COMPETITORS, competitorsJson); diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/AbstractJsonExportTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/AbstractJsonExportTest.java index 9cbc93bc25a..bc6cf71f321 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/AbstractJsonExportTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/AbstractJsonExportTest.java @@ -89,11 +89,11 @@ public abstract class AbstractJsonExportTest { BoatClass boatClass = new BoatClassImpl("505", /* typicallyStartsUpwind */ true); for (int i = 1; i <= numberOfCompetitorsToCreate; i++) { String competitorName = "C" + i; - Competitor competitor = new CompetitorImpl(UUID.randomUUID(), competitorName, Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl(competitorName, new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is famous "+competitorName)), - new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), - /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat", + Competitor competitor = new CompetitorImpl(UUID.randomUUID(), competitorName, Color.RED, null, new TeamImpl("STG", Collections.singleton( + new PersonImpl(competitorName, new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is famous "+competitorName)), + new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), + /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat", boatClass, null)); result.add(competitor); } diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/RaceLogSerializationTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/RaceLogSerializationTest.java index 27bf9672fff..9df60e2713f 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/RaceLogSerializationTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/RaceLogSerializationTest.java @@ -49,8 +49,8 @@ public class RaceLogSerializationTest { private JsonDeserializer deserializer; private final AbstractLogEventAuthor author = new LogEventAuthorImpl("test", 0); private static final Competitor competitor = new CompetitorImpl("a", "b", null, - new TeamImpl("a", Collections.singletonList(new PersonImpl("a", new NationalityImpl("GER"), new Date(), "abc")), null), - new BoatImpl("a", new BoatClassImpl("a", true), "abc")); + null, + new TeamImpl("a", Collections.singletonList(new PersonImpl("a", new NationalityImpl("GER"), new Date(), "abc")), null), new BoatImpl("a", new BoatClassImpl("a", true), "abc")); @Before public void setup() { diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/AbstractJaxRsApiTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/AbstractJaxRsApiTest.java index 308954f2376..6ec2d9293be 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/AbstractJaxRsApiTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/AbstractJaxRsApiTest.java @@ -70,11 +70,11 @@ public abstract class AbstractJaxRsApiTest { BoatClass boatClass = new BoatClassImpl("505", /* typicallyStartsUpwind */ true); for (int i = 1; i <= numberOfCompetitorsToCreate; i++) { String competitorName = "C" + i; - Competitor competitor = new CompetitorImpl(123, competitorName, Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl(competitorName, new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is famous "+competitorName)), - new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), - /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat", + Competitor competitor = new CompetitorImpl(123, competitorName, Color.RED, null, new TeamImpl("STG", Collections.singleton( + new PersonImpl(competitorName, new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is famous "+competitorName)), + new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), + /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat", boatClass, null)); result.add(competitor); } diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/CompetitorsResourceTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/CompetitorsResourceTest.java index 43855aa01f0..4c04bd95597 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/CompetitorsResourceTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/CompetitorsResourceTest.java @@ -34,7 +34,7 @@ public class CompetitorsResourceTest extends AbstractJaxRsApiTest { super.setUp(); DynamicTeam team = new TeamImpl(null, Collections.singleton(new PersonImpl(null, new NationalityImpl(nationality), null, null)), null); DynamicBoat boat = new BoatImpl(null, new BoatClassImpl(boatClassName, false), sailID); - racingEventService.getBaseDomainFactory().getOrCreateCompetitor(id, name, null, team, boat); + racingEventService.getBaseDomainFactory().getOrCreateCompetitor(id, name, null, null, team, boat); } @Test diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceCheckinAndOutTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceCheckinAndOutTest.java index e5302d47c0a..29a39aed17c 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceCheckinAndOutTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceCheckinAndOutTest.java @@ -44,8 +44,8 @@ public class LeaderboardsResourceCheckinAndOutTest extends AbstractJaxRsApiTest public void setUp() { super.setUp(); Competitor c = createCompetitors(1).get(0); - competitor = racingEventService.getBaseDomainFactory().getOrCreateCompetitor(c.getId(), c.getName(), c.getColor(), - (DynamicTeam) c.getTeam(), (DynamicBoat) c.getBoat()); + competitor = racingEventService.getBaseDomainFactory().getOrCreateCompetitor(c.getId(), c.getName(), + c.getColor(), c.getEmail(), (DynamicTeam) c.getTeam(), (DynamicBoat) c.getBoat()); Regatta regatta = new RegattaImpl("regatta", new BoatClassImpl("49er", false), MillisecondsTimePoint.now(), MillisecondsTimePoint.now(), Collections.singleton(new SeriesImpl("series", false, Collections @@ -62,7 +62,7 @@ public class LeaderboardsResourceCheckinAndOutTest extends AbstractJaxRsApiTest LeaderboardsResource resource = spyResource(new LeaderboardsResource()); UUID deviceUuid = UUID.randomUUID(); - //checkin + // checkin long fromMillis = 500; JSONObject json = new JSONObject(); json.put(DeviceMappingConstants.JSON_COMPETITOR_ID_AS_STRING, competitor.getId().toString()); @@ -82,22 +82,24 @@ public class LeaderboardsResourceCheckinAndOutTest extends AbstractJaxRsApiTest DeviceMapping mappingForC = mappingsForC.get(0); assertThat("that mapping is for the correct device", ((SmartphoneUUIDIdentifier) mappingForC.getDevice()).getUUID(), equalTo(deviceUuid)); - assertThat("that mapping starts at the correct timepoint", mappingForC.getTimeRange().from().asMillis(), equalTo(fromMillis)); + assertThat("that mapping starts at the correct timepoint", mappingForC.getTimeRange().from().asMillis(), + equalTo(fromMillis)); assertThat("that mapping is open-ended", mappingForC.getTimeRange().hasOpenEnd(), equalTo(true)); - - //checkout + + // checkout long toMillis = 1000; json = new JSONObject(); json.put(DeviceMappingConstants.JSON_TO_MILLIS, toMillis); json.put(DeviceMappingConstants.JSON_COMPETITOR_ID_AS_STRING, competitor.getId().toString()); json.put(DeviceMappingConstants.JSON_DEVICE_UUID, deviceUuid.toString()); - + response = resource.postCheckout(json.toString(), leaderboard.getName()); assertThat("checkout returns OK", response.getStatus(), equalTo(Response.Status.OK.getStatusCode())); - + mappings = new DeviceCompetitorMappingFinder<>(log).analyze(); mappingForC = mappings.get(competitor).get(0); - assertThat("mapping now ends at checkout timepoint", mappingForC.getTimeRange().to().asMillis(), equalTo(toMillis)); + assertThat("mapping now ends at checkout timepoint", mappingForC.getTimeRange().to().asMillis(), + equalTo(toMillis)); } } diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/TeamImageTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/TeamImageTest.java index 61576a65b08..0c6b0ccb51f 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/TeamImageTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/TeamImageTest.java @@ -54,7 +54,7 @@ public class TeamImageTest extends AbstractJaxRsApiTest { DynamicTeam team = new TeamImpl(null, Collections.singleton(new PersonImpl(null, new NationalityImpl( nationality), null, null)), null); DynamicBoat boat = new BoatImpl(null, new BoatClassImpl(boatClassName, false), sailID); - racingEventService.getBaseDomainFactory().getOrCreateCompetitor(id, name, null, team, boat); + racingEventService.getBaseDomainFactory().getOrCreateCompetitor(id, name, null, null, team, boat); } @Test diff --git a/java/com.sap.sailing.server.gateway/.classpath b/java/com.sap.sailing.server.gateway/.classpath index eca7bdba8f0..1747f410a23 100644 --- a/java/com.sap.sailing.server.gateway/.classpath +++ b/java/com.sap.sailing.server.gateway/.classpath @@ -1,7 +1,8 @@ - - - - - - - + + + + + + + + diff --git a/java/com.sap.sailing.server.gateway/META-INF/MANIFEST.MF b/java/com.sap.sailing.server.gateway/META-INF/MANIFEST.MF index 41fcf805f1d..a36f2b8ba90 100644 --- a/java/com.sap.sailing.server.gateway/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.server.gateway/META-INF/MANIFEST.MF @@ -48,7 +48,13 @@ Require-Bundle: com.sap.sailing.domain, com.sap.sailing.server.gateway.serialization.shared.android, com.sap.sse.shared.android, com.sap.sse.filestorage, - com.sun.jersey.contribs.jersey-multipart;bundle-version="1.17.1" + com.sun.jersey.contribs.jersey-multipart;bundle-version="1.17.0", + com.sap.sse.security, + org.apache.commons.math;bundle-version="2.1.0", + org.apache.shiro.core;bundle-version="1.2.2", + org.apache.shiro.web;bundle-version="1.2.2", + org.apache.shiro.ehcache;bundle-version="1.2.3", + org.jvnet.mimepull;bundle-version="1.9.4" Bundle-ClassPath: . Web-ContextPath: /sailingserver Export-Package: com.sap.sailing.server.gateway, diff --git a/java/com.sap.sailing.server.gateway/WEB-INF/web.xml b/java/com.sap.sailing.server.gateway/WEB-INF/web.xml index 95efad80403..fec58ee8e20 100644 --- a/java/com.sap.sailing.server.gateway/WEB-INF/web.xml +++ b/java/com.sap.sailing.server.gateway/WEB-INF/web.xml @@ -16,6 +16,30 @@ + + + shiroEnvironmentClass + org.apache.shiro.web.env.IniWebEnvironment + + + org.apache.shiro.web.env.EnvironmentLoaderListener + + + ShiroFilter + org.apache.shiro.web.servlet.ShiroFilter + + + + ShiroFilter + /* + REQUEST + FORWARD + INCLUDE + ERROR + + AdminApp com.sap.sailing.server.gateway.impl.AdminApp @@ -230,7 +254,15 @@ TrackFilesImportServlet /trackfiles/import - + + FileUploadServlet + com.sap.sailing.server.gateway.impl.FileUploadServlet + + + FileUploadServlet + /fileupload + + websockettest com.sap.sailing.server.gateway.impl.WebSocketTestServlet @@ -253,12 +285,12 @@ com.sap.sailing.server.gateway.jaxrs.AccessControlAndEncodingResponseFilter 1 - - + + Jersey REST API /api/* - - + + Jersey REST SPI com.sap.sailing.server.gateway.jaxrs.RestServletContainer @@ -270,10 +302,10 @@ com.sap.sailing.server.gateway.jaxrs.AccessControlAndEncodingResponseFilter 1 - - + + Jersey REST SPI /spi/* - + \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway/build.properties b/java/com.sap.sailing.server.gateway/build.properties index 2c55f73f136..d178f8e4452 100644 --- a/java/com.sap.sailing.server.gateway/build.properties +++ b/java/com.sap.sailing.server.gateway/build.properties @@ -1,4 +1,5 @@ -source.. = src/ +source.. = src/,\ + resources/ output.. = bin/ bin.includes = META-INF/,\ .,\ diff --git a/java/com.sap.sailing.server.gateway/resources/shiro.ini b/java/com.sap.sailing.server.gateway/resources/shiro.ini new file mode 100755 index 00000000000..f1635ef547f --- /dev/null +++ b/java/com.sap.sailing.server.gateway/resources/shiro.ini @@ -0,0 +1,64 @@ + +[main] +credentialsMatcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher +credentialsMatcher.storedCredentialsHexEncoded = false +credentialsMatcher.hashIterations = 1024 + +# Realm configuration: +# -------------------- + +permissionsForRoleProvider = com.sap.sailing.domain.common.security.SailingPermissionsForRoleProvider +upRealm = com.sap.sse.security.UsernamePasswordRealm +upRealm.credentialsMatcher = $credentialsMatcher +upRealm.permissionsForRoleProvider = $permissionsForRoleProvider +oauthRealm = com.sap.sse.security.OAuthRealm +oauthRealm.permissionsForRoleProvider = $permissionsForRoleProvider +bearerTokenRealm = com.sap.sse.security.BearerTokenRealm +bearerTokenRealm.permissionsForRoleProvider = $permissionsForRoleProvider + +# Putting it all together: +sessionManager = com.sap.sse.security.SecurityWebSessionManager +securityManager.sessionManager = $sessionManager +sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO +securityManager.sessionManager.sessionDAO = $sessionDAO +cacheManager = com.sap.sse.security.SessionCacheManager +securityManager.cacheManager = $cacheManager + +# Authentication Filter Configurations +# ------------------------------------ + +# A filter that requires the user to have at least one of the roles specified as +# filter parameter. This makes it different from the default roles[...] filter +# which requires the user to have ALL of the roles specified as parameters. +anyofroles = com.sap.sse.security.AnyOfRolesFilter + +# A custom filter for GWT pages that require an authenticated user. +# Forwards unauthenticated users to the default login page. When +# addressed directly (and not based on a redirect), users who logged +# on successfully will be redirected to the UserManagement.html page +# where they can adjust their profile. +customGwt = com.sap.sse.security.CustomFilter +customGwt.loginUrl = /security/ui/Login.html +customGwt.successUrl = /UserManagement.html + +# Configuration for the default shiro HTTP form authentication filter. +# It assumes that form-based login parameters are sent to the +# /api/restsecurity/login RESTlet and forwards successful login attempts +# to the /api/restsecurity/hello service which is expected to respond +# with a JSON document containing the authenticated subject's properties. +authc.loginUrl = /api/restsecurity/login +authc.successUrl = /api/restsecurity/hello + +# This authentication filter accepts a bearer access token in the HTTP +# Authorization header field, as in +# Authorization: Bearer 1029741026501365024376093245 +# If no such bearer token is provided, the filter falls back to basic HTTP +# authentication ("Authentication: Basic ...") and then regular form-based +# authentication with POST parameters "username" and "password". +bearerToken = com.sap.sse.security.BearerTokenOrBasicOrFormAuthenticationFilter + +# Specifying filter chains for URL patterns + +[urls] +# Uncomment the following to require the permission "event:view" in order to use /api/v1/events: +# /api/v1/events = bearerToken, perms["event:view"] diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/AbstractFileUploadServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/AbstractFileUploadServlet.java new file mode 100755 index 00000000000..7c712f22d94 --- /dev/null +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/AbstractFileUploadServlet.java @@ -0,0 +1,47 @@ +package com.sap.sailing.server.gateway.impl; + +import java.io.IOException; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.fileupload.FileItem; +import org.apache.commons.fileupload.FileItemFactory; +import org.apache.commons.fileupload.FileUploadException; +import org.apache.commons.fileupload.disk.DiskFileItemFactory; +import org.apache.commons.fileupload.servlet.ServletFileUpload; + +import com.sap.sailing.server.gateway.AbstractJsonHttpServlet; + +/** + * Abstract servlet handling multipart-mime file upload coming from an upload form + * + * @author Fredrik Teschke + * @author Axel Uhl + * + */ +public abstract class AbstractFileUploadServlet extends AbstractJsonHttpServlet { + public static final String PREFERRED_IMPORTER = "preferredImporter"; + private static final long serialVersionUID = 1120226743039934620L; + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + if (!ServletFileUpload.isMultipartContent(req)) { + resp.sendError(HttpServletResponse.SC_BAD_REQUEST); + return; + } + FileItemFactory factory = new DiskFileItemFactory(); + ServletFileUpload upload = new ServletFileUpload(factory); + try { + @SuppressWarnings("unchecked") + List items = (List) upload.parseRequest(req); + process(items, req, resp); + } catch (FileUploadException e) { + throw new IOException("Could not parse request"); + } + } + + abstract protected void process(List fileItems, HttpServletRequest req, HttpServletResponse resp) throws IOException; +} diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/EventRaceStatesJsonGetServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/EventRaceStatesJsonGetServlet.java index e769a35ed4e..1897a5ec74a 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/EventRaceStatesJsonGetServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/EventRaceStatesJsonGetServlet.java @@ -1,8 +1,6 @@ package com.sap.sailing.server.gateway.impl; import java.io.IOException; -import java.util.Calendar; -import java.util.Date; import java.util.UUID; import java.util.logging.Logger; @@ -10,28 +8,13 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.json.simple.JSONArray; import org.json.simple.JSONObject; -import com.sap.sailing.domain.abstractlog.race.RaceLog; -import com.sap.sailing.domain.abstractlog.race.RaceLogFlagEvent; -import com.sap.sailing.domain.abstractlog.race.analyzing.impl.AbortingFlagFinder; -import com.sap.sailing.domain.abstractlog.race.analyzing.impl.FinishedTimeFinder; -import com.sap.sailing.domain.abstractlog.race.analyzing.impl.LastFlagsFinder; -import com.sap.sailing.domain.abstractlog.race.analyzing.impl.StartTimeFinder; -import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState; -import com.sap.sailing.domain.abstractlog.race.state.impl.ReadonlyRaceStateImpl; -import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.gate.ReadonlyGateStartRacingProcedure; -import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.Event; -import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.RaceColumn; -import com.sap.sailing.domain.common.RaceIdentifier; -import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.leaderboard.Leaderboard; -import com.sap.sailing.domain.racelog.RaceStateOfSameDayHelper; import com.sap.sailing.server.gateway.AbstractJsonHttpServlet; -import com.sap.sse.common.TimePoint; +import com.sap.sailing.server.gateway.jaxrs.api.EventRaceStatesSerializer; +import com.sap.sse.common.Util.Pair; public class EventRaceStatesJsonGetServlet extends AbstractJsonHttpServlet { private static final long serialVersionUID = -4820965681871902242L; @@ -39,33 +22,17 @@ public class EventRaceStatesJsonGetServlet extends AbstractJsonHttpServlet { private final static Logger logger = Logger.getLogger(EventRaceStatesJsonGetServlet.class.getName()); private static final String PARAM_NAME_EVENTID = "eventId"; - // private static final String PARAM_NAME_FILTER_BY_REGATTA = "filterByRegatta"; private static final String PARAM_NAME_FILTER_BY_LEADERBOARD = "filterByLeaderboard"; private static final String PARAM_NAME_FILTER_BY_COURSEAREA = "filterByCourseArea"; private static final String PARAM_NAME_FILTER_BY_DAYOFFSET = "filterByDayOffset"; - public static final String FIELD_EVENT_NAME = "name"; - public static final String FIELD_EVENT_ID = "id"; - public static final String FIELD_RACE_STATES = "raceStates"; - @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String eventIdParam = request.getParameter(PARAM_NAME_EVENTID); String filterByCourseAreaParam = request.getParameter(PARAM_NAME_FILTER_BY_COURSEAREA); String filterByLeaderboardParam = request.getParameter(PARAM_NAME_FILTER_BY_LEADERBOARD); String filterByDayOffsetParam = request.getParameter(PARAM_NAME_FILTER_BY_DAYOFFSET); - boolean filterByDayOffset = false; - Calendar dayToCheck = Calendar.getInstance(); - dayToCheck.setTime(new Date()); - if(filterByDayOffsetParam != null) { - try { - int dayOffset = Integer.parseInt(filterByDayOffsetParam); - filterByDayOffset = true; - dayToCheck.add(Calendar.DAY_OF_YEAR, dayOffset); - } catch (NumberFormatException e) { - // invalid integer - } - } + if (eventIdParam == null) { response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "You need to specify a event id using the "+ PARAM_NAME_EVENTID + " parameter"); @@ -74,98 +41,15 @@ public class EventRaceStatesJsonGetServlet extends AbstractJsonHttpServlet { if (event == null) { response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Event "+eventIdParam+" not found"); } else { - JSONObject result = new JSONObject(); - result.put(FIELD_EVENT_NAME, event.getName()); - result.put(FIELD_EVENT_ID, event.getId().toString()); - JSONArray raceStatesLogEntriesJson = new JSONArray(); - result.put(FIELD_RACE_STATES, raceStatesLogEntriesJson); - for (CourseArea courseArea : event.getVenue().getCourseAreas()) { - if(filterByCourseAreaParam == null || courseArea.getName().equals(filterByCourseAreaParam)) { - for (Leaderboard leaderboard : getService().getLeaderboards().values()) { - if (filterByLeaderboardParam == null || leaderboard.getName().equals(filterByLeaderboardParam)) { - if (leaderboard.getDefaultCourseArea() != null && leaderboard.getDefaultCourseArea().equals(courseArea)) { - String leaderboardName = leaderboard.getName(); - String leaderboardDisplayName = leaderboard.getDisplayName(); - for (RaceColumn raceColumn : leaderboard.getRaceColumns()) { - for (Fleet fleet : raceColumn.getFleets()) { - if(!filterByDayOffset || isRaceStateOfSameDay(raceColumn, fleet, dayToCheck)) { - JSONObject raceStateJson = createRaceStateJsonObject(raceColumn, fleet); - raceStateJson.put("courseAreaName", courseArea.getName()); - raceStateJson.put("leaderboardName", leaderboardName); - raceStateJson.put("leaderboardDisplayName", leaderboardDisplayName); - raceStatesLogEntriesJson.add(raceStateJson); - } - } - } - } - } - } - } - } + EventRaceStatesSerializer eventRaceStatesSerializer = new EventRaceStatesSerializer(filterByCourseAreaParam, + filterByLeaderboardParam, filterByDayOffsetParam); + JSONObject result = eventRaceStatesSerializer.serialize(new Pair>(event, getService().getLeaderboards().values())); setJsonResponseHeader(response); result.writeJSONString(response.getWriter()); } } } - private boolean isRaceStateOfSameDay(RaceColumn raceColumn, Fleet fleet, Calendar now) { - boolean result = false; - RaceLog raceLog = raceColumn.getRaceLog(fleet); - if (raceLog != null && !raceLog.isEmpty()) { - TimePoint startTime = new StartTimeFinder(raceLog).analyze(); - TimePoint finishedTime = new FinishedTimeFinder(raceLog).analyze(); - RaceLogFlagEvent abortingFlagEvent = new AbortingFlagFinder(raceLog).analyze(); - TimePoint abortingTime = abortingFlagEvent != null ? abortingFlagEvent.getLogicalTimePoint() : null; - - result = RaceStateOfSameDayHelper.isRaceStateOfSameDay(startTime, finishedTime, abortingTime, now); - } - return result; - } - - private JSONObject createRaceStateJsonObject(RaceColumn raceColumn, Fleet fleet) { - JSONObject result = new JSONObject(); - result.put("raceName", raceColumn.getName()); - result.put("fleetName", fleet.getName()); - RaceIdentifier raceIdentifier = raceColumn.getRaceIdentifier(fleet); - result.put("trackedRaceName", raceIdentifier != null ? raceIdentifier.getRaceName() : null); - result.put("trackedRaceId", raceIdentifier != null ? raceIdentifier.toString() : null); - RaceLog raceLog = raceColumn.getRaceLog(fleet); - if (raceLog != null && !raceLog.isEmpty()) { - ReadonlyRaceState state = ReadonlyRaceStateImpl.create(raceLog); - RaceLogRaceStatus status = state.getStatus(); - TimePoint startTime = state.getStartTime(); - TimePoint finishedTime = state.getFinishedTime(); - JSONObject raceLogStateJson = new JSONObject(); - result.put("raceState", raceLogStateJson); - raceLogStateJson.put("startTime", startTime != null ? startTime.toString() : null); - raceLogStateJson.put("endTime", finishedTime != null ? finishedTime.toString() : null); - raceLogStateJson.put("lastStatus", status.name()); - ReadonlyGateStartRacingProcedure procedure = state.getTypedReadonlyRacingProcedure(ReadonlyGateStartRacingProcedure.class); - if (procedure != null) { - raceLogStateJson.put("pathfinderId", procedure.getPathfinder()); - raceLogStateJson.put("gateLineOpeningTime", procedure.getGateLaunchStopTime()); - } - AbortingFlagFinder abortingFlagFinder = new AbortingFlagFinder(raceLog); - RaceLogFlagEvent abortingFlagEvent = abortingFlagFinder.analyze(); - LastFlagsFinder lastFlagFinder = new LastFlagsFinder(raceLog); - RaceLogFlagEvent lastFlagEvent = LastFlagsFinder.getMostRecent(lastFlagFinder.analyze()); - if (lastFlagEvent != null) { - setLastFlagField(raceLogStateJson, lastFlagEvent.getUpperFlag().name(), lastFlagEvent.getLowerFlag().name(), lastFlagEvent.isDisplayed()); - } else if (status.equals(RaceLogRaceStatus.UNSCHEDULED) && abortingFlagEvent != null) { - setLastFlagField(raceLogStateJson, abortingFlagEvent.getUpperFlag().name(), abortingFlagEvent.getLowerFlag().name(), abortingFlagEvent.isDisplayed()); - } else { - setLastFlagField(raceLogStateJson, null, null, null); - } - } - return result; - } - - private void setLastFlagField(JSONObject raceLogStateJson, String upperFlagName, String lowerFlagName, Boolean isDisplayed) { - raceLogStateJson.put("lastUpperFlag", upperFlagName); - raceLogStateJson.put("lastLowerFlag", lowerFlagName); - raceLogStateJson.put("displayed", isDisplayed); - } - /** * Tries to convert the given identifier to a UUID. When the identifier is not a UUID, null is returned. * @param identifierToConvert the identifier as a String diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java new file mode 100755 index 00000000000..15d05cc0f1d --- /dev/null +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java @@ -0,0 +1,80 @@ +package com.sap.sailing.server.gateway.impl; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.apache.commons.fileupload.FileItem; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +import com.sap.sse.common.NoCorrespondingServiceRegisteredException; +import com.sap.sse.filestorage.InvalidPropertiesException; +import com.sap.sse.filestorage.OperationFailedException; + +/** + * Accepts a multi-part MIME encoded set of files. Returns an array of JSONObjects that each contain + * a "file_uri" value, in the same order in which the parts were sent. + * + * @author Axel Uhl (d043530) + * + */ +public class FileUploadServlet extends AbstractFileUploadServlet { + private static final long serialVersionUID = -9002541098579359029L; + + private static final Logger logger = Logger.getLogger(FileUploadServlet.class.getName()); + + public static final String JSON_FILE_NAME = "file_name"; + + public static final String JSON_FILE_URI = "file_uri"; + + /** + * The maximum size of an image uploaded by a user as a team image, in megabytes (1024*1024 bytes) + */ + private static final int MAX_SIZE_IN_MB = 5; + + @Override + protected void process(List fileItems, HttpServletRequest req, HttpServletResponse resp) throws UnsupportedEncodingException, IOException { + /** + * Expects the HTTP header {@code Content-Length} to be set. + */ + final JSONArray resultList = new JSONArray(); + for (FileItem fileItem : fileItems) { + JSONObject result = new JSONObject(); + String fileExtension = ""; + String fileType = fileItem.getContentType(); + if (fileType.equals("image/jpeg")) { + fileExtension = ".jpg"; + } else if (fileType.equals("image/png")) { + fileExtension = ".png"; + } + URI fileUri; + try { + if (fileItem.getSize() > 1024 * 1024 * MAX_SIZE_IN_MB) { + throw new WebApplicationException(Response.status(Status.BAD_REQUEST) + .entity("Image is larger than " + MAX_SIZE_IN_MB + "MB").build()); + } + fileUri = getService().getFileStorageManagementService().getActiveFileStorageService() + .storeFile(fileItem.getInputStream(), fileExtension, fileItem.getSize()); + result.put(JSON_FILE_NAME, fileItem.getName()); + result.put(JSON_FILE_URI, fileUri.toString()); + } catch (IOException | OperationFailedException | InvalidPropertiesException | NoCorrespondingServiceRegisteredException e) { + final String errorMessage = "Could not store file"+ (e.getMessage()==null?"":(": " + e.getMessage())); + logger.log(Level.WARNING, "Could not store file", e); + result.put("status", Status.INTERNAL_SERVER_ERROR.name()); + result.put("message", errorMessage); + } + resultList.add(result); + } + resp.getOutputStream().write(resultList.toJSONString().getBytes("UTF-8")); + } +} diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/RegattaAndRaceDataJsonGetServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/RegattaAndRaceDataJsonGetServlet.java index 6b9ff74828d..cb774ad9a47 100755 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/RegattaAndRaceDataJsonGetServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/RegattaAndRaceDataJsonGetServlet.java @@ -372,7 +372,7 @@ public class RegattaAndRaceDataJsonGetServlet extends AbstractJsonHttpServlet { jsonEvent.put("boatclass", regatta.getBoatClass().getName()); } JSONArray jsonCompetitors = new JSONArray(); - for (Competitor competitor : regatta.getCompetitors()) { + for (Competitor competitor : regatta.getAllCompetitors()) { JSONObject jsonCompetitor = new JSONObject(); jsonCompetitor.put("name", competitor.getName()); jsonCompetitor.put("sailID", competitor.getBoat()==null?"":competitor.getBoat().getSailID()); diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/AccessControlAndEncodingResponseFilter.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/AccessControlAndEncodingResponseFilter.java index 0031f89ddd0..cc95ae05fcd 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/AccessControlAndEncodingResponseFilter.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/AccessControlAndEncodingResponseFilter.java @@ -15,8 +15,6 @@ public class AccessControlAndEncodingResponseFilter implements ContainerResponse httpHeaders.add("Access-Control-Allow-Headers", "Authorization, Origin, X-Requested-With, Content-Type"); httpHeaders.add("Access-Control-Expose-Headers", "Location, Content-Disposition"); httpHeaders.add("Access-Control-Allow-Methods", "POST, PUT, GET, DELETE, HEAD, OPTIONS"); - httpHeaders.remove("Content-Type"); - httpHeaders.add("Content-Type", "application/json;charset=UTF-8"); return response; } } \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/CompetitorsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/CompetitorsResource.java index 7b864d75085..7d793b04760 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/CompetitorsResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/CompetitorsResource.java @@ -111,6 +111,7 @@ public class CompetitorsResource extends AbstractSailingServerResource { @POST @Consumes({ "image/jpeg", "image/png" }) @Path("{competitor-id}/team/image") + @Produces("application/json;charset=UTF-8") public String setTeamImage(@PathParam("competitor-id") String competitorId, InputStream uploadedInputStream, @HeaderParam("Content-Type") String fileType, @HeaderParam("Content-Length") long sizeInBytes) { @@ -144,7 +145,7 @@ public class CompetitorsResource extends AbstractSailingServerResource { .entity("Could not store competitor image").type(MediaType.TEXT_PLAIN).build()); } - getService().getCompetitorStore().updateCompetitor(competitorId, competitor.getName(), competitor.getColor(), + getService().getCompetitorStore().updateCompetitor(competitorId, competitor.getName(), competitor.getColor(), competitor.getEmail(), competitor.getBoat().getSailID(), competitor.getTeam().getNationality(), imageUri); logger.log(Level.INFO, "Set team image for competitor " + competitor.getName()); diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventRaceStatesSerializer.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventRaceStatesSerializer.java new file mode 100644 index 00000000000..7b3e83dc649 --- /dev/null +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventRaceStatesSerializer.java @@ -0,0 +1,87 @@ +package com.sap.sailing.server.gateway.jaxrs.api; + +import java.util.Calendar; +import java.util.Date; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +import com.sap.sailing.domain.base.CourseArea; +import com.sap.sailing.domain.base.Event; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.server.gateway.serialization.JsonSerializer; +import com.sap.sse.common.Util.Pair; + +public class EventRaceStatesSerializer implements JsonSerializer>> { + public static final String FIELD_EVENT_NAME = "name"; + public static final String FIELD_EVENT_ID = "id"; + public static final String FIELD_RACE_STATES = "raceStates"; + + private String filterByCourseArea; + private String filterByLeaderboardName; + private Calendar filterDay; + + public EventRaceStatesSerializer() { + this.filterByCourseArea = null; + this.filterByLeaderboardName = null; + this.filterDay = null; + } + + public EventRaceStatesSerializer(String filterByCourseArea, String filterByLeaderboard, String filterByDayOffset) { + this.filterByCourseArea = filterByCourseArea; + this.filterByLeaderboardName = filterByLeaderboard; + + Integer dayOffset = null; + if(filterByDayOffset != null) { + try { + dayOffset = Integer.parseInt(filterByDayOffset); + filterDay = Calendar.getInstance(); + filterDay.setTime(new Date()); + filterDay.add(Calendar.DAY_OF_YEAR, dayOffset); + } catch (NumberFormatException e) { + // invalid integer + filterDay = null; + } + } + } + + @Override + public JSONObject serialize(Pair> eventAndLeaderboards) { + Event event = eventAndLeaderboards.getA(); + Iterable leaderboards = eventAndLeaderboards.getB(); + + JSONObject result = new JSONObject(); + result.put(FIELD_EVENT_NAME, event.getName()); + result.put(FIELD_EVENT_ID, event.getId().toString()); + JSONArray raceStatesLogEntriesJson = new JSONArray(); + result.put(FIELD_RACE_STATES, raceStatesLogEntriesJson); + RaceStateSerializer raceStateSerializer = new RaceStateSerializer(); + for (CourseArea courseArea : event.getVenue().getCourseAreas()) { + if(filterByCourseArea == null || courseArea.getName().equals(filterByCourseArea)) { + for (Leaderboard leaderboard : leaderboards) { + if (filterByLeaderboardName == null || leaderboard.getName().equals(filterByLeaderboardName)) { + if (leaderboard.getDefaultCourseArea() != null && leaderboard.getDefaultCourseArea().equals(courseArea)) { + String leaderboardName = leaderboard.getName(); + String leaderboardDisplayName = leaderboard.getDisplayName(); + for (RaceColumn raceColumn : leaderboard.getRaceColumns()) { + for (Fleet fleet : raceColumn.getFleets()) { + Pair raceColumnAndFleet = new Pair(raceColumn, fleet); + if(filterDay == null || raceStateSerializer.isRaceStateOfSameDay(raceColumnAndFleet, filterDay)) { + JSONObject raceStateJson = raceStateSerializer.serialize(raceColumnAndFleet); + raceStateJson.put("courseAreaName", courseArea.getName()); + raceStateJson.put("leaderboardName", leaderboardName); + raceStateJson.put("leaderboardDisplayName", leaderboardDisplayName); + raceStatesLogEntriesJson.add(raceStateJson); + } + } + } + } + } + } + } + } + return result; + } +} diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java index ce7a2f336a9..6da6328ffc9 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java @@ -6,6 +6,7 @@ import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; @@ -15,12 +16,14 @@ import org.json.simple.JSONObject; import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.EventBase; +import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.server.gateway.jaxrs.AbstractSailingServerResource; import com.sap.sailing.server.gateway.serialization.JsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.CourseAreaJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.EventBaseJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.LeaderboardGroupBaseJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.VenueJsonSerializer; +import com.sap.sse.common.Util.Pair; @Path("/v1/events") public class EventsResource extends AbstractSailingServerResource { @@ -71,8 +74,26 @@ public class EventsResource extends AbstractSailingServerResource { @GET @Produces("application/json;charset=UTF-8") @Path("{eventId}/racestates") - public Response getRaceStates(@PathParam("eventId") String eventId) { - return Response.noContent().build(); + public Response getRaceStates(@PathParam("eventId") String eventId, @QueryParam("filterByLeaderboard") String filterByLeaderboard, + @QueryParam("filterByCourseArea") String filterByCourseArea, @QueryParam("filterByDayOffset") String filterByDayOffset) { + Response response; + UUID eventUuid; + try { + eventUuid = UUID.fromString(eventId); + } catch (IllegalArgumentException e) { + return getBadEventErrorResponse(eventId); + } + Event event = getService().getEvent(eventUuid); + if (event == null) { + response = getBadEventErrorResponse(eventId); + } else { + EventRaceStatesSerializer eventRaceStatesSerializer = new EventRaceStatesSerializer(filterByCourseArea, + filterByLeaderboard, filterByDayOffset); + JSONObject raceStatesJson = eventRaceStatesSerializer.serialize(new Pair>(event, getService().getLeaderboards().values())); + String json = raceStatesJson.toJSONString(); + response = Response.ok(json, MediaType.APPLICATION_JSON).build(); + } + return response; } } \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java new file mode 100755 index 00000000000..bb027ede091 --- /dev/null +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java @@ -0,0 +1,79 @@ +package com.sap.sailing.server.gateway.jaxrs.api; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; +import javax.ws.rs.core.Response.Status; + +import org.json.simple.JSONObject; + +import com.sap.sailing.server.gateway.impl.FileUploadServlet; +import com.sap.sailing.server.gateway.jaxrs.AbstractSailingServerResource; +import com.sap.sse.common.NoCorrespondingServiceRegisteredException; +import com.sap.sse.filestorage.InvalidPropertiesException; +import com.sap.sse.filestorage.OperationFailedException; + +/** + * File upload is handled by {@link FileUploadServlet} because Jersey has issues with multipart MIME file posting. This + * resource only handles the DELETE method. + * + * @author Axel Uhl (d043530) + * + */ +@Path("/v1/file") +public class FileStorageResource extends AbstractSailingServerResource { + private static final Logger logger = Logger.getLogger(FileStorageResource.class.getName()); + + @GET + public Response getFile(@QueryParam("uri") String uri) { + Response response; + try { + InputStream inputStream = new URL(uri).openStream(); + ResponseBuilder responseBuilder = Response.ok().entity(inputStream); + if (uri.toLowerCase().endsWith(".jpg")) { + responseBuilder.header("Content-Type", "image/jpeg"); + } else if (uri.toLowerCase().endsWith(".png")) { + responseBuilder.header("Content-Type", "image/png"); + } + response = responseBuilder.build(); + } catch (IOException ioe) { + response = Response.status(Status.BAD_REQUEST).entity(ioe.getMessage()).build(); + } + return response; + } + + // Example test use: + // curl -d "uri=file:///c:/tmp/c7b821e1-ebab-4a96-a71d-28ac192b3e69.jpg" http://127.0.0.1:8888/sailingserver/api/v1/file + @POST + @Produces("application/json;charset=UTF-8") + public Response deleteFile(@QueryParam("uri") String uri) { + final JSONObject result = new JSONObject(); + Response response; + try { + getService().getFileStorageManagementService().getActiveFileStorageService() + .removeFile(new URI(uri)); + result.put("status", Status.OK.name()); + response = Response.ok().entity(result.toJSONString()).build(); + } catch (NoCorrespondingServiceRegisteredException | OperationFailedException | InvalidPropertiesException + | URISyntaxException | IOException e) { + final String errorMessage = "Could not delete file with URI "+uri+": "+e.getMessage(); + logger.log(Level.WARNING, "Could not delete file with URI "+uri, e); + result.put("status", Status.BAD_REQUEST.name()); + result.put("message", errorMessage); + response = Response.status(Status.BAD_REQUEST).entity(result.toJSONString()).build(); + } + return response; + } +} diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/PolarResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/PolarResource.java index c8af43fc78b..c7660d073cb 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/PolarResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/PolarResource.java @@ -1,6 +1,7 @@ package com.sap.sailing.server.gateway.jaxrs.api; import java.util.ArrayList; +import java.util.Set; import java.util.stream.Stream; import javax.ws.rs.GET; @@ -13,6 +14,7 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.core.Response.Status; +import org.apache.commons.math.analysis.polynomials.PolynomialFunction; import org.json.simple.JSONArray; import com.sap.sailing.domain.base.BoatClass; @@ -42,10 +44,10 @@ import com.sap.sse.common.Util.Pair; import com.sap.sse.util.kmeans.Cluster; /** - * Right now this service is only used for quick debugging and testing of the polar api. It used plain text responses. - * JSON should be used if this resource is to be consumed. + * Right now this service is only used for quick debugging and testing of the polar api. Some services use plain text + * responses. JSON or other common formats should be used if the series are to be consumed in production. * - * @author Frederik Petersen + * @author Frederik Petersen / Axel Uhl (maneuver-based wind estimation) * */ @Path("/v1/polars") @@ -73,6 +75,34 @@ public class PolarResource extends AbstractSailingServerResource { return responseBuilder.build(); } + @GET + @Produces("text/plain;charset=UTF-8") + @Path("functions") + public Response getFunctions() { + PolarDataService polarDataService = getService().getPolarDataService(); + Set boatClasses = polarDataService.getAllBoatClassesWithPolarSheetsAvailable(); + ResponseBuilder responseBuilder; + StringBuilder stringBuilder = new StringBuilder(); + for (BoatClass boatClass : boatClasses) { + for (LegType legType : new LegType[] { LegType.UPWIND, LegType.DOWNWIND }) { + for (Tack tack : new Tack[] { Tack.PORT, Tack.STARBOARD }) { + try { + PolynomialFunction speedFunction = polarDataService.getSpeedRegressionFunction(boatClass, legType, tack); + stringBuilder.append("Speed: " + boatClass + " " + legType + " " + tack + ": " + speedFunction.toString() + "\n"); + PolynomialFunction angleFunction = polarDataService.getAngleRegressionFunction(boatClass, legType, tack); + stringBuilder.append("Angle: " + boatClass + " " + legType + " " + tack + ": " + angleFunction.toString() + "\n"); + } catch (NotEnoughDataHasBeenAddedException e) { + stringBuilder.append("No data for " + boatClass + " " + legType + " " + tack + "\n"); + } + } + } + stringBuilder.append("\n\n"); + } + + responseBuilder = Response.ok(stringBuilder.toString(), MediaType.TEXT_PLAIN); + return responseBuilder.build(); + } + @GET @Produces("text/plain;charset=UTF-8") @Path("average/{boatClassName}") @@ -85,7 +115,7 @@ public class PolarResource extends AbstractSailingServerResource { try { PolarDataService service = getService().getPolarDataService(); SpeedWithBearingWithConfidence speedWithBearing = service.getAverageSpeedWithBearing(boatClass, - windSpeed, legType, tack); + windSpeed, legType, tack, true); String resultString = "Speed: " + speedWithBearing.getObject().getKnots() + "kn; Angle: " + speedWithBearing.getObject().getBearing().getDegrees() + "°; Confidence: " + speedWithBearing.getConfidence(); diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RaceStateSerializer.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RaceStateSerializer.java new file mode 100644 index 00000000000..ba4e7484024 --- /dev/null +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RaceStateSerializer.java @@ -0,0 +1,90 @@ +package com.sap.sailing.server.gateway.jaxrs.api; + +import java.util.Calendar; + +import org.json.simple.JSONObject; + +import com.sap.sailing.domain.abstractlog.race.RaceLog; +import com.sap.sailing.domain.abstractlog.race.RaceLogFlagEvent; +import com.sap.sailing.domain.abstractlog.race.analyzing.impl.AbortingFlagFinder; +import com.sap.sailing.domain.abstractlog.race.analyzing.impl.FinishedTimeFinder; +import com.sap.sailing.domain.abstractlog.race.analyzing.impl.LastFlagsFinder; +import com.sap.sailing.domain.abstractlog.race.analyzing.impl.StartTimeFinder; +import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState; +import com.sap.sailing.domain.abstractlog.race.state.impl.ReadonlyRaceStateImpl; +import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.gate.ReadonlyGateStartRacingProcedure; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; +import com.sap.sailing.domain.racelog.RaceStateOfSameDayHelper; +import com.sap.sailing.server.gateway.serialization.JsonSerializer; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util.Pair; + +public class RaceStateSerializer implements JsonSerializer> { + @Override + public JSONObject serialize(Pair raceColumnAndFleet) { + RaceColumn raceColumn = raceColumnAndFleet.getA(); + Fleet fleet = raceColumnAndFleet.getB(); + + JSONObject result = new JSONObject(); + result.put("raceName", raceColumn.getName()); + result.put("fleetName", fleet.getName()); + RaceIdentifier raceIdentifier = raceColumn.getRaceIdentifier(fleet); + result.put("trackedRaceName", raceIdentifier != null ? raceIdentifier.getRaceName() : null); + result.put("trackedRaceId", raceIdentifier != null ? raceIdentifier.toString() : null); + RaceLog raceLog = raceColumn.getRaceLog(fleet); + if (raceLog != null && !raceLog.isEmpty()) { + ReadonlyRaceState state = ReadonlyRaceStateImpl.create(raceLog); + RaceLogRaceStatus status = state.getStatus(); + TimePoint startTime = state.getStartTime(); + TimePoint finishedTime = state.getFinishedTime(); + JSONObject raceLogStateJson = new JSONObject(); + result.put("raceState", raceLogStateJson); + raceLogStateJson.put("startTime", startTime != null ? startTime.toString() : null); + raceLogStateJson.put("endTime", finishedTime != null ? finishedTime.toString() : null); + raceLogStateJson.put("lastStatus", status.name()); + ReadonlyGateStartRacingProcedure procedure = state.getTypedReadonlyRacingProcedure(ReadonlyGateStartRacingProcedure.class); + if (procedure != null) { + raceLogStateJson.put("pathfinderId", procedure.getPathfinder()); + raceLogStateJson.put("gateLineOpeningTime", procedure.getGateLaunchStopTime()); + } + AbortingFlagFinder abortingFlagFinder = new AbortingFlagFinder(raceLog); + RaceLogFlagEvent abortingFlagEvent = abortingFlagFinder.analyze(); + LastFlagsFinder lastFlagFinder = new LastFlagsFinder(raceLog); + RaceLogFlagEvent lastFlagEvent = LastFlagsFinder.getMostRecent(lastFlagFinder.analyze()); + if (lastFlagEvent != null) { + setLastFlagField(raceLogStateJson, lastFlagEvent.getUpperFlag().name(), lastFlagEvent.getLowerFlag().name(), lastFlagEvent.isDisplayed()); + } else if (status.equals(RaceLogRaceStatus.UNSCHEDULED) && abortingFlagEvent != null) { + setLastFlagField(raceLogStateJson, abortingFlagEvent.getUpperFlag().name(), abortingFlagEvent.getLowerFlag().name(), abortingFlagEvent.isDisplayed()); + } else { + setLastFlagField(raceLogStateJson, null, null, null); + } + } + return result; + } + + private void setLastFlagField(JSONObject raceLogStateJson, String upperFlagName, String lowerFlagName, Boolean isDisplayed) { + raceLogStateJson.put("lastUpperFlag", upperFlagName); + raceLogStateJson.put("lastLowerFlag", lowerFlagName); + raceLogStateJson.put("displayed", isDisplayed); + } + + public boolean isRaceStateOfSameDay(Pair raceColumnAndFleet, Calendar dayToCheck) { + RaceColumn raceColumn = raceColumnAndFleet.getA(); + Fleet fleet = raceColumnAndFleet.getB(); + + boolean result = false; + RaceLog raceLog = raceColumn.getRaceLog(fleet); + if (raceLog != null && !raceLog.isEmpty()) { + TimePoint startTime = new StartTimeFinder(raceLog).analyze(); + TimePoint finishedTime = new FinishedTimeFinder(raceLog).analyze(); + RaceLogFlagEvent abortingFlagEvent = new AbortingFlagFinder(raceLog).analyze(); + TimePoint abortingTime = abortingFlagEvent != null ? abortingFlagEvent.getLogicalTimePoint() : null; + + result = RaceStateOfSameDayHelper.isRaceStateOfSameDay(startTime, finishedTime, abortingTime, dayToCheck); + } + return result; + } +} diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java index 751720ade13..f1ae8aa36fc 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java @@ -7,6 +7,8 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; @@ -65,6 +67,7 @@ import com.sap.sse.common.impl.MillisecondsTimePoint; @Path("/v1/regattas") public class RegattasResource extends AbstractSailingServerResource { + private static final Logger logger = Logger.getLogger(RegattasResource.class.getName()); private Response getBadRegattaErrorResponse(String regattaName) { return Response.status(Status.NOT_FOUND).entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN).build(); @@ -651,6 +654,9 @@ public class RegattasResource extends AbstractSailingServerResource { jsonCompetitorInLeg.put("jibes", numberOfJibes); jsonCompetitorInLeg.put("penaltyCircles", numberOfPenaltyCircles); } catch (NoWindException e) { + logger.log(Level.FINE, + "No wind information while trying to determing maneuvers for competitor " + + competitor.getName(), e); } TimePoint startTime = trackedLegOfCompetitor.getStartTime(); diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RestApiApplication.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RestApiApplication.java index 0002c3620a3..39f37809050 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RestApiApplication.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RestApiApplication.java @@ -17,6 +17,7 @@ public class RestApiApplication extends Application { classes.add(SearchResource.class); classes.add(GPSFixesResource.class); classes.add(CompetitorsResource.class); + classes.add(FileStorageResource.class); return classes; } } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/TrackFilesImportServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/TrackFilesImportServlet.java index 0c40dfdf8b5..8c58abc1db6 100755 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/TrackFilesImportServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/TrackFilesImportServlet.java @@ -15,15 +15,10 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; -import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.fileupload.FileItem; -import org.apache.commons.fileupload.FileItemFactory; -import org.apache.commons.fileupload.FileUploadException; -import org.apache.commons.fileupload.disk.DiskFileItemFactory; -import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.osgi.framework.Filter; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; @@ -35,7 +30,7 @@ import com.sap.sailing.domain.trackimport.FormatNotSupportedException; import com.sap.sailing.domain.trackimport.GPSFixImporter; import com.sap.sailing.domain.trackimport.GPSFixImporter.Callback; import com.sap.sailing.domain.tracking.GPSFix; -import com.sap.sailing.server.gateway.AbstractJsonHttpServlet; +import com.sap.sailing.server.gateway.impl.AbstractFileUploadServlet; import com.sap.sse.common.NoCorrespondingServiceRegisteredException; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util.Pair; @@ -57,7 +52,7 @@ import com.sap.sse.common.Util.Pair; * @author Fredrik Teschke * */ -public class TrackFilesImportServlet extends AbstractJsonHttpServlet { +public class TrackFilesImportServlet extends AbstractFileUploadServlet { public static final String PREFERRED_IMPORTER = "preferredImporter"; private static final long serialVersionUID = 1120226743039934620L; private static final Logger logger = Logger.getLogger(TrackFilesImportServlet.class.getName()); @@ -159,26 +154,11 @@ public class TrackFilesImportServlet extends AbstractJsonHttpServlet { return deviceIds; } - @SuppressWarnings("unchecked") @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - if (!ServletFileUpload.isMultipartContent(req)) { - resp.sendError(HttpServletResponse.SC_BAD_REQUEST); - return; - } - - Set> files = new HashSet<>(); - FileItemFactory factory = new DiskFileItemFactory(); - ServletFileUpload upload = new ServletFileUpload(factory); - List items; - try { - items = (List) upload.parseRequest(req); - } catch (FileUploadException e) { - throw new IOException("Could not parse request"); - } - + protected void process(List fileItems, HttpServletRequest req, HttpServletResponse resp) throws IOException { String prefImporterType = null; - for (FileItem item : items) { + List> files = new ArrayList<>(); + for (FileItem item : fileItems) { if (!item.isFormField()) files.add(new Pair(item.getName(), item.getInputStream())); else { diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/competitorTeamGetDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/competitorTeamGetDoc.html new file mode 100644 index 00000000000..8cb27f6a6b8 --- /dev/null +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/competitorTeamGetDoc.html @@ -0,0 +1,46 @@ + + + + + + + + SAP Sailing Analytics Webservices API Version 1.0 Beta + + +

    SAP Sailing Analytics Webservices API Version 1.0 Beta

    +

    URL: /api/v1/competitors/{id}/team

    + +Description: +

    Gets the details of a competitor's team

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Webservice Type:REST
    Output format:Json
    Mandatory parameters:None
    Optional parameters:None
    Request method:GET
    Example:http://www.sapsailing.com/sailingserver/api/v1/competitors/bba11294-9930-11e0-85be-406186cbf87c/team
    +
    +Back to Web Service Overview + + diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/competitorTeamImagePostDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/competitorTeamImagePostDoc.html new file mode 100644 index 00000000000..b94a26965db --- /dev/null +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/competitorTeamImagePostDoc.html @@ -0,0 +1,61 @@ + + + + + + + + SAP Sailing Analytics Webservices API Version 1.0 Beta + + +

    SAP Sailing Analytics Webservices API Version 1.0 Beta

    +

    URL: /api/v1/competitors/{id}/team/image

    + +Description: +

    Sets the team image

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Webservice Type:REST
    Output format:JSON
    Mandatory headers:Content-Type: (image-png|image-jpg)
     Content-Length
    Mandatory parameters:None
    Optional parameters:None
    Request method:POST (Binary data, not multipart)
    Request example:
    curl -H "Content-Type:image/jpeg" --data-binary @{image-path} \
    +   http://sapsailing.com/sailingserver/api/v1/competitors/{competitor-id}/team/image
    Response example:
    {
    +  "teamImageUri" : "http://training.sapsailing.com/team_images/9871d3a2c554b27151cacf1422eec048.jpeg"
    +}
    +
    +Back to Web Service Overview + + diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/eventRaceStatesGetDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/eventRaceStatesGetDoc.html new file mode 100644 index 00000000000..c1ceefc855f --- /dev/null +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/eventRaceStatesGetDoc.html @@ -0,0 +1,50 @@ + + + + + + + + SAP Sailing Analytics Webservices API Version 1.0 Beta + + +

    SAP Sailing Analytics Webservices API Version 1.0 Beta

    +

    URL: /api/v1/events/{eventId}/racestates

    + +Description: +

    The service gets the race states of all races of an sailing event

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Webservice Type:REST
    Output format:Json
    Mandatory parameters:None
    Optional parameters: +
    filterByLeaderboard (filters the race states by the name of the leaderboard)
    +
    filterByCourseArea (filters the race states by the name of the course area)
    +
    filterByDayOffset (filters the race states by a day offset for current day, e.g. 1 means yesterday)
    +
    Request method:GET
    Example:http://www.sapsailing.com/sailingserver/api/v1/events/41de25fd-0f22-4200-bdac-5574e7d5225d/racestates
    +
    +Back to Web Service Overview + + diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html index 766f399ca1a..fd83b6ab453 100644 --- a/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html @@ -40,6 +40,10 @@ name to make sure you get to the event you're interested in. /api/v1/events/{id} Gets the details of an event + + /api/v1/events/{id}/racestates + Gets the race states of all races of an event + /api/v1/regattas Gets all regattas @@ -96,6 +100,18 @@ name to make sure you get to the event you're interested in. /api/v1/leaderboards/{name} Gets the details of a leaderboard + + /api/v1/leaderboards/{name}/device_mappings/start + Tracking App Device Checkin on leaderboard + + + /api/v1/leaderboards/{name}/device_mappings/end + Tracking App Device Checkout on leaderboard + + + /api/v1/leaderboards/{name}/competitors/{id} + Gets the details of a competitor in the context of a leaderboard + /api/v1/gps_fixes Adds GPS-fixes that originated from a device with a unique identifier @@ -105,8 +121,12 @@ name to make sure you get to the event you're interested in. Gets the details of a competitor - /api/v1/leaderboards/{name}/competitors/{id} - Gets the details of a competitor in the context of a leaderboard + /api/v1/competitors/{id}/team + Gets the details of a competitor's team + + + /api/v1/competitors/{id}/team/image + Sets the team image diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/leaderboardCheckinPostDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/leaderboardCheckinPostDoc.html new file mode 100644 index 00000000000..f56526a95b3 --- /dev/null +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/leaderboardCheckinPostDoc.html @@ -0,0 +1,56 @@ + + + + + + + + SAP Sailing Analytics Webservices API Version 1.0 Beta + + +

    SAP Sailing Analytics Webservices API Version 1.0 Beta

    +

    URL: /api/v1/leaderboards/{name}/device_mappings/start

    + +Description: +

    Tracking App Device Checkin on leaderboard

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Webservice Type:REST
    Output format:HTTP 200 (no body)
    Mandatory parameters:None
    Optional parameters:None
    Request method:POST
    Mandatory input:JSON
     
    {
    +    "competitorId" : "af855a56-9726-4a9c-a77e-da955bd289bg",
    +    "deviceType" : "ios",
    +    "deviceUuid" : "af855a56-9726-4a9c-a77e-da955bd289bf",
    +    "pushDeviceId" : "0f744707bebcf74f9b7c25d48e3358945f6aa01da5ddb387462c7eaf61bbad78",
    +    "fromMillis" : 1415624564718
    +}
    +
    +Back to Web Service Overview + + diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/leaderboardCheckoutPostDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/leaderboardCheckoutPostDoc.html new file mode 100644 index 00000000000..8be664a81e1 --- /dev/null +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/leaderboardCheckoutPostDoc.html @@ -0,0 +1,54 @@ + + + + + + + + SAP Sailing Analytics Webservices API Version 1.0 Beta + + +

    SAP Sailing Analytics Webservices API Version 1.0 Beta

    +

    URL: /api/v1/leaderboards/{name}/device_mappings/end

    + +Description: +

    Tracking App Device Checkout on leaderboard

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Webservice Type:REST
    Output format:HTTP 200 (no body)
    Mandatory parameters:None
    Optional parameters:None
    Request method:POST
    Mandatory input:JSON
     
    {
    +    "competitorId" : "af855a56-9726-4a9c-a77e-da955bd289bg",
    +    "deviceUuid" : "af855a56-9726-4a9c-a77e-da955bd289bf",
    +    "toMillis" : 1415624564719
    +}
    +
    +Back to Web Service Overview + + diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/leaderboardCompetitorGetDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/leaderboardCompetitorGetDoc.html new file mode 100644 index 00000000000..3dbd8660aa3 --- /dev/null +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/leaderboardCompetitorGetDoc.html @@ -0,0 +1,46 @@ + + + + + + + + SAP Sailing Analytics Webservices API Version 1.0 Beta + + +

    SAP Sailing Analytics Webservices API Version 1.0 Beta

    +

    URL: /api/v1/leaderboards/{name}/competitors/{competitorId}

    + +Description: +

    Gets the leaderboard-specific version of a competitor

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Webservice Type:REST
    Output format:Json
    Mandatory parameters:None
    Optional parameters:None
    Request method:GET
    Example:http://www.sapsailing.com/sailingserver/api/v1/leaderboards/{name}/competitors/{id}
    +
    +Back to Web Service Overview + + diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractLogReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractLogReplicationTest.java index 44d0a431ec4..5d9524cd142 100644 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractLogReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractLogReplicationTest.java @@ -35,8 +35,9 @@ public abstract class AbstractLogReplicationTest { - protected MongoDBService mongoDBService; - protected MongoObjectFactory mongoObjectFactory; +public abstract class AbstractServerReplicationTest extends com.sap.sse.replication.testsupport.AbstractServerWithSingleServiceReplicationTest { + protected ServerReplicationTestSetUp testSetUp; - /** - * Drops the test DB, if dropDB is true and requests the DB to start. - */ - @Override - protected void persistenceSetUp(boolean dropDB) { - mongoDBService = MongoDBService.INSTANCE; - if (dropDB) { - mongoDBService.getDB().dropDatabase(); - } - mongoObjectFactory = PersistenceFactory.INSTANCE.getMongoObjectFactory(mongoDBService); + protected AbstractServerReplicationTest(ServerReplicationTestSetUp testSetUp) { + super(testSetUp); + this.testSetUp = testSetUp; + } + + public AbstractServerReplicationTest() { + super(new ServerReplicationTestSetUp()); + testSetUp = (ServerReplicationTestSetUp) super.testSetUp; } - protected RacingEventServiceImpl createNewMaster() { - return new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoDBService, - DomainFactory.INSTANCE), mongoObjectFactory, MediaDBFactory.INSTANCE.getMediaDB(mongoDBService), - EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE); - } - - protected RacingEventServiceImpl createNewReplica() { - return new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoDBService, - // replica gets its own base DomainFactory: - new DomainFactoryImpl()), mongoObjectFactory, MediaDBFactory.INSTANCE.getMediaDB(mongoDBService), - EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE); + protected static class ServerReplicationTestSetUp extends com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp { + protected MongoDBService mongoDBService; + protected MongoObjectFactory mongoObjectFactory; + + /** + * Drops the test DB, if dropDB is true and requests the DB to start. + */ + @Override + protected void persistenceSetUp(boolean dropDB) { + mongoDBService = MongoDBService.INSTANCE; + if (dropDB) { + mongoDBService.getDB().dropDatabase(); + } + mongoObjectFactory = PersistenceFactory.INSTANCE.getMongoObjectFactory(mongoDBService); + } + + @Override + public RacingEventServiceImpl createNewMaster() { + return new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoDBService, + DomainFactory.INSTANCE), mongoObjectFactory, MediaDBFactory.INSTANCE.getMediaDB(mongoDBService), + EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE); + } + + @Override + public RacingEventServiceImpl createNewReplica() { + return new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoDBService, + // replica gets its own base DomainFactory: + new DomainFactoryImpl()), mongoObjectFactory, MediaDBFactory.INSTANCE.getMediaDB(mongoDBService), + EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE); + } } } diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/CompetitorReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/CompetitorReplicationTest.java index a26e7f3dbf1..0756050f030 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/CompetitorReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/CompetitorReplicationTest.java @@ -76,7 +76,7 @@ public class CompetitorReplicationTest extends AbstractServerReplicationTest { Iterable emptyWaypointList = Collections.emptyList(); final String competitorName = "Der mit dem Kiel zieht"; Competitor competitor = master.getBaseDomainFactory().getOrCreateCompetitor( - 123, competitorName, Color.RED, + 123, competitorName, Color.RED, "someone@nowhere.de", new TeamImpl("STG", Collections.singleton(new PersonImpl(competitorName, new NationalityImpl("GER"), /* dateOfBirth */null, "This is famous " + competitorName)), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), @@ -106,7 +106,7 @@ public class CompetitorReplicationTest extends AbstractServerReplicationTest { // now update competitor on master using replicating operation final String newCompetitorName = "Der Vogel, der mit dem Kiel zieht"; - master.apply(new UpdateCompetitor(competitor.getId().toString(), newCompetitorName, competitor.getColor(), competitor.getBoat().getSailID(), competitor.getTeam().getNationality(), + master.apply(new UpdateCompetitor(competitor.getId().toString(), newCompetitorName, competitor.getColor(), competitor.getEmail(), competitor.getBoat().getSailID(), competitor.getTeam().getNationality(), competitor.getTeam().getImage())); Thread.sleep(1000); assertEquals(newCompetitorName, replicatedCompetitor.getName()); // expect in-place update of existing competitor in replica @@ -114,7 +114,7 @@ public class CompetitorReplicationTest extends AbstractServerReplicationTest { // now allow for resetting to default through some event, such as receiving a GPS position master.apply(new AllowCompetitorResetToDefaults(Collections.singleton(competitor.getId().toString()))); // modify the competitor on the master "from below" without an UpdateCompetitor operation, only locally: - master.getBaseDomainFactory().getCompetitorStore().updateCompetitor(competitor.getId().toString(), competitorName, Color.RED, + master.getBaseDomainFactory().getCompetitorStore().updateCompetitor(competitor.getId().toString(), competitorName, Color.RED, competitor.getEmail(), competitor.getBoat().getSailID(), competitor.getTeam().getNationality(), competitor.getTeam().getImage()); final RegattaAndRaceIdentifier raceIdentifier = masterRegatta.getRaceIdentifier(raceDefinition); DynamicTrackedRace trackedRace = (DynamicTrackedRace) master.apply(new CreateTrackedRace(raceIdentifier, diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ConnectionResetAndReconnectTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ConnectionResetAndReconnectTest.java index 30ce408fa99..170313fa30c 100644 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ConnectionResetAndReconnectTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ConnectionResetAndReconnectTest.java @@ -12,7 +12,6 @@ import java.util.List; import java.util.UUID; import java.util.logging.Logger; -import org.junit.Before; import org.junit.Test; import com.rabbitmq.client.Channel; @@ -33,6 +32,10 @@ import com.sap.sse.replication.impl.ReplicationMasterDescriptorImpl; public class ConnectionResetAndReconnectTest extends AbstractServerReplicationTest { static final Logger logger = Logger.getLogger(ConnectionResetAndReconnectTest.class.getName()); + public ConnectionResetAndReconnectTest() { + super(new ServerReplicationTestSetUp()); + } + public static boolean forceStopDelivery = false; static class QueuingConsumerTest extends QueueingConsumer { @@ -81,16 +84,19 @@ public class ConnectionResetAndReconnectTest extends AbstractServerReplicationTe } - @Before - @Override - public void setUp() throws Exception { - try { - Pair, ReplicationMasterDescriptor> result = basicSetUp(/* dropDB */ true, - /* master=null means create a new one */ null, /* replica=null means create a new one */null); - masterDescriptor = MasterReplicationDescriptorMock.from(result.getB()); - } catch (Exception e) { - e.printStackTrace(); - tearDown(); + private static class ServerReplicationTestSetUp extends + com.sap.sailing.server.replication.test.AbstractServerReplicationTest.ServerReplicationTestSetUp { + @Override + public void setUp() throws Exception { + try { + Pair, ReplicationMasterDescriptor> result = basicSetUp( + /* dropDB */true, + /* master=null means create a new one */null, /* replica=null means create a new one */null); + masterDescriptor = MasterReplicationDescriptorMock.from(result.getB()); + } catch (Exception e) { + e.printStackTrace(); + tearDown(); + } } } diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/DelayedLeaderboardCorrectionsReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/DelayedLeaderboardCorrectionsReplicationTest.java index c28f2ea50e6..9bb2e1db068 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/DelayedLeaderboardCorrectionsReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/DelayedLeaderboardCorrectionsReplicationTest.java @@ -46,6 +46,7 @@ import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.mongodb.MongoDBService; import com.sap.sse.replication.ReplicationMasterDescriptor; import com.sap.sse.replication.ReplicationService; +import com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp.ReplicationServiceTestImpl; public class DelayedLeaderboardCorrectionsReplicationTest extends AbstractServerReplicationTest { private static final Logger logger = Logger.getLogger(DelayedLeaderboardCorrectionsReplicationTest.class.getName()); @@ -115,7 +116,7 @@ public class DelayedLeaderboardCorrectionsReplicationTest extends AbstractServer masterLeaderboardReloaded.getRaceColumnByName(Q2), MillisecondsTimePoint.now())); // replicate the re-loaded environment - Pair, ReplicationMasterDescriptor> descriptors = basicSetUp( + Pair, ReplicationMasterDescriptor> descriptors = basicSetUp( /* dropDB */false, master, replica); replicaReplicator = descriptors.getA(); masterDescriptor = descriptors.getB(); diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java index 862220bc514..a2b082373bd 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java @@ -73,9 +73,10 @@ import com.sap.sse.common.impl.MillisecondsDurationImpl; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.replication.ReplicationMasterDescriptor; import com.sap.sse.replication.impl.ReplicationReceiver; +import com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp.ReplicationServiceTestImpl; public class InitialLoadReplicationObjectIdentityTest extends AbstractServerReplicationTest { - private Pair, ReplicationMasterDescriptor> replicationDescriptorPair; + private Pair, ReplicationMasterDescriptor> replicationDescriptorPair; /** * Drops the test DB. Sets up master and replica, starts the JMS message broker and registers the replica with the master. @@ -84,10 +85,10 @@ public class InitialLoadReplicationObjectIdentityTest extends AbstractServerRepl @Override public void setUp() throws Exception { persistenceSetUp(/* dropDB */ true); - this.master = new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoDBService, DomainFactory.INSTANCE), PersistenceFactory.INSTANCE - .getMongoObjectFactory(mongoDBService), MediaDBFactory.INSTANCE.getMediaDB(mongoDBService), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE); - this.replica = new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoDBService, DomainFactory.INSTANCE), PersistenceFactory.INSTANCE - .getMongoObjectFactory(mongoDBService), MediaDBFactory.INSTANCE.getMediaDB(mongoDBService), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE); + this.master = new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(testSetUp.mongoDBService, DomainFactory.INSTANCE), PersistenceFactory.INSTANCE + .getMongoObjectFactory(testSetUp.mongoDBService), MediaDBFactory.INSTANCE.getMediaDB(testSetUp.mongoDBService), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE); + this.replica = new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(testSetUp.mongoDBService, DomainFactory.INSTANCE), PersistenceFactory.INSTANCE + .getMongoObjectFactory(testSetUp.mongoDBService), MediaDBFactory.INSTANCE.getMediaDB(testSetUp.mongoDBService), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE); } private void performReplicationSetup() throws Exception { diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/PrematureOperationReceiptTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/PrematureOperationReceiptTest.java index 6e640221b30..18d90dc0e82 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/PrematureOperationReceiptTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/PrematureOperationReceiptTest.java @@ -5,7 +5,6 @@ import static org.junit.Assert.assertNull; import java.io.IOException; -import org.junit.Before; import org.junit.Test; import com.sap.sailing.domain.leaderboard.Leaderboard; @@ -17,17 +16,19 @@ import com.sap.sse.replication.ReplicationMasterDescriptor; import com.sap.sse.replication.impl.ReplicationReceiver; public class PrematureOperationReceiptTest extends AbstractServerReplicationTest { - private ReplicationReceiver replicator; + private static ReplicationReceiver replicator; + + public PrematureOperationReceiptTest() { + super(new ServerReplicationTestSetUp()); + } - /** - * Drops the test DB. Sets up master and replica, starts the JMS message broker and registers the replica with the master. - */ - @Before - @Override - public void setUp() throws Exception { - Pair, ReplicationMasterDescriptor> result = - basicSetUp(/* dropDB */ true, /* master=null means create a new one */ null, /* replica=null means create a new one */ null); - replicator = replicaReplicator.startToReplicateFromButDontYetFetchInitialLoad(result.getB(), /* startReplicatorSuspended */ true); + private static class ServerReplicationTestSetUp extends AbstractServerReplicationTest.ServerReplicationTestSetUp { + @Override + public void setUp() throws Exception { + Pair, ReplicationMasterDescriptor> result = + basicSetUp(/* dropDB */ true, /* master=null means create a new one */ null, /* replica=null means create a new one */ null); + replicator = replicaReplicator.startToReplicateFromButDontYetFetchInitialLoad(result.getB(), /* startReplicatorSuspended */ true); + } } /** diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RaceLogReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RaceLogReplicationTest.java index 9660d8b92f3..44fb244da54 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RaceLogReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RaceLogReplicationTest.java @@ -38,35 +38,23 @@ import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; -import com.sap.sailing.server.RacingEventService; import com.sap.sailing.server.operationaltransformation.AddColumnToLeaderboard; import com.sap.sailing.server.operationaltransformation.AddColumnToSeries; import com.sap.sailing.server.operationaltransformation.CreateRegattaLeaderboard; import com.sap.sailing.server.operationaltransformation.RenameLeaderboard; import com.sap.sse.common.Util; -import com.sap.sse.common.Util.Pair; import com.sap.sse.common.impl.MillisecondsTimePoint; -import com.sap.sse.replication.ReplicationMasterDescriptor; public class RaceLogReplicationTest extends AbstractLogReplicationTest { - - private Pair, ReplicationMasterDescriptor> replicationDescriptorPair; private RaceLogEvent raceLogEvent; private RaceLogEvent anotherRaceLogEvent; private AbstractLogEventAuthor author = new LogEventAuthorImpl("Test Author", 1); @Before - @Override - public void setUp() throws Exception { + public void createEvents() throws Exception { raceLogEvent = RaceLogEventFactory.INSTANCE.createRaceStatusEvent(new MillisecondsTimePoint(1), author, 42, RaceLogRaceStatus.UNKNOWN); anotherRaceLogEvent = RaceLogEventFactory.INSTANCE.createRaceStatusEvent(new MillisecondsTimePoint(2), author, 42, RaceLogRaceStatus.UNKNOWN); - try { - replicationDescriptorPair = basicSetUp(/* dropDB */ true, null, null); - } catch (Exception e) { - e.printStackTrace(); - tearDown(); - } } @Test @@ -77,7 +65,7 @@ public class RaceLogReplicationTest extends AbstractLogReplicationTest { - - private Pair, ReplicationMasterDescriptor> replicationDescriptorPair; private RegattaLogEvent regattaLogEvent; // private RegattaLogEvent anotherRegattaLogEvent; @@ -37,17 +32,10 @@ public class RegattaLogReplicationTest extends } @Before - @Override - public void setUp() throws Exception { + public void createEvent() throws Exception { final Competitor competitor = TrackBasedTest.createCompetitor("Test Competitor"); regattaLogEvent = new RegattaLogRegisterCompetitorEventImpl(t(0), author, t(0), 0, competitor); // anotherRegattaLogEvent = new RegattaLogRegisterCompetitorEventImpl(t(1), author, t(1), 1, null); - try { - replicationDescriptorPair = basicSetUp(/* dropDB */ true, null, null); - } catch (Exception e) { - e.printStackTrace(); - tearDown(); - } } @Test @@ -57,7 +45,7 @@ public class RegattaLogReplicationTest extends final String fleetName = "Default"; Regatta regatta = setupRegatta(RegattaImpl.getDefaultName(regattaName, BOAT_CLASS_NAME_49er), seriesName, fleetName, BOAT_CLASS_NAME_49er); RegattaLog masterLog = regatta.getRegattaLog(); - replicationDescriptorPair.getA().startToReplicateFrom(replicationDescriptorPair.getB()); + replicaReplicator.startToReplicateFrom(masterDescriptor); RegattaLog replicaLog = getReplicaLog(regatta); addAndValidateEventIds(masterLog, replicaLog); } @@ -70,7 +58,7 @@ public class RegattaLogReplicationTest extends Regatta regatta = setupRegatta(RegattaImpl.getDefaultName(regattaName, BOAT_CLASS_NAME_49er), seriesName, fleetName, BOAT_CLASS_NAME_49er); RegattaLog masterLog = regatta.getRegattaLog(); masterLog.add(regattaLogEvent); - replicationDescriptorPair.getA().startToReplicateFrom(replicationDescriptorPair.getB()); + replicaReplicator.startToReplicateFrom(masterDescriptor); RegattaLog replicaLog = getReplicaLog(regatta); addAndValidateEventIds(masterLog, replicaLog); } @@ -83,7 +71,7 @@ public class RegattaLogReplicationTest extends Regatta masterRegatta = setupRegatta(RegattaImpl.getDefaultName(regattaName, BOAT_CLASS_NAME_49er), seriesName, fleetName, BOAT_CLASS_NAME_49er); RegattaLog masterLog = masterRegatta.getRegattaLog(); masterLog.add(regattaLogEvent); - replicationDescriptorPair.getA().startToReplicateFrom(replicationDescriptorPair.getB()); + replicaReplicator.startToReplicateFrom(masterDescriptor); RegattaLog replicaLog = getReplicaLog(masterRegatta); addAndValidateEventIds(masterLog, replicaLog, regattaLogEvent); } @@ -94,7 +82,7 @@ public class RegattaLogReplicationTest extends FlexibleLeaderboard masterLeaderboard = setupFlexibleLeaderboard(leaderboardName); RegattaLog masterLog = masterLeaderboard.getRegattaLog(); masterLog.add(regattaLogEvent); - replicationDescriptorPair.getA().startToReplicateFrom(replicationDescriptorPair.getB()); + replicaReplicator.startToReplicateFrom(masterDescriptor); RegattaLog replicaLog = getReplicaLog(masterLeaderboard); addAndValidateEventIds(masterLog, replicaLog, regattaLogEvent); } diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackedRaceContentsReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackedRaceContentsReplicationTest.java index 047ecd2589c..d045f487598 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackedRaceContentsReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackedRaceContentsReplicationTest.java @@ -71,7 +71,7 @@ public class TrackedRaceContentsReplicationTest extends AbstractServerReplicatio // FIXME use master DomainFactory; see bug 592 final DomainFactory masterDomainFactory = DomainFactory.INSTANCE; BoatClass boatClass = masterDomainFactory.getOrCreateBoatClass(boatClassName, /* typicallyStartsUpwind */true); - competitor = masterDomainFactory.getCompetitorStore().getOrCreateCompetitor("GER 61", "Tina Lutz", Color.RED, new TeamImpl("Tina Lutz + Susann Beucke", + competitor = masterDomainFactory.getCompetitorStore().getOrCreateCompetitor("GER 61", "Tina Lutz", Color.RED, "someone@nowhere.de", new TeamImpl("Tina Lutz + Susann Beucke", (List) Arrays.asList(new PersonImpl[] { new PersonImpl("Tina Lutz", DomainFactory.INSTANCE.getOrCreateNationality("GER"), null, null), new PersonImpl("Tina Lutz", DomainFactory.INSTANCE.getOrCreateNationality("GER"), null, null) }), new PersonImpl("Rigo de Mas", DomainFactory.INSTANCE.getOrCreateNationality("NED"), null, null)), diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/CompetitorStoreTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/CompetitorStoreTest.java index e26d57c9e15..5b4a1c95bf1 100755 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/CompetitorStoreTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/CompetitorStoreTest.java @@ -22,20 +22,20 @@ public class CompetitorStoreTest { public void testAddingToTransientStore() { CompetitorStore transientStore = new TransientCompetitorStoreImpl(); DynamicCompetitor template = AbstractLeaderboardTest.createCompetitor("Test Competitor"); - Competitor competitor = transientStore.getOrCreateCompetitor(template.getId(), template.getName(), template.getColor(), template.getTeam(), template.getBoat()); + Competitor competitor = transientStore.getOrCreateCompetitor(template.getId(), template.getName(), template.getColor(), template.getEmail(), template.getTeam(), template.getBoat()); assertTrue(competitor != template); - Competitor competitor2 = transientStore.getOrCreateCompetitor(template.getId(), template.getName(), template.getColor(), template.getTeam(), template.getBoat()); + Competitor competitor2 = transientStore.getOrCreateCompetitor(template.getId(), template.getName(), template.getColor(), template.getEmail(), template.getTeam(), template.getBoat()); assertSame(competitor, competitor2); assertEquals(DomainFactory.INSTANCE.getOrCreateNationality("GER"), competitor.getTeam().getNationality()); DynamicTeam differentTeam = AbstractLeaderboardTest.createCompetitor("Test Competitor").getTeam(); differentTeam.setNationality(DomainFactory.INSTANCE.getOrCreateNationality("GHA")); // Ghana - Competitor competitor3 = transientStore.getOrCreateCompetitor(template.getId(), template.getName(), template.getColor(), differentTeam, template.getBoat()); + Competitor competitor3 = transientStore.getOrCreateCompetitor(template.getId(), template.getName(), template.getColor(), template.getEmail(), differentTeam, template.getBoat()); assertSame(competitor, competitor3); // use existing competitor despite the different team assertSame(competitor.getTeam(), competitor3.getTeam()); // now mark the competitor as to update from defaults transientStore.allowCompetitorResetToDefaults(competitor); - Competitor competitor4 = transientStore.getOrCreateCompetitor(template.getId(), template.getName(), template.getColor(), differentTeam, template.getBoat()); + Competitor competitor4 = transientStore.getOrCreateCompetitor(template.getId(), template.getName(), template.getColor(), template.getEmail(), differentTeam, template.getBoat()); assertSame(competitor, competitor4); // expecting an in-place update assertEquals(differentTeam.getNationality(), competitor4.getTeam().getNationality()); } @@ -44,7 +44,7 @@ public class CompetitorStoreTest { public void testPersistentCompetitorStore() { CompetitorStore persistentStore1 = new PersistentCompetitorStore(PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(), /* clearStore */ true, null); DynamicCompetitor template = AbstractLeaderboardTest.createCompetitor("Test Competitor"); - Competitor competitor = persistentStore1.getOrCreateCompetitor(template.getId(), template.getName(), template.getColor(), template.getTeam(), template.getBoat()); + Competitor competitor = persistentStore1.getOrCreateCompetitor(template.getId(), template.getName(), template.getColor(), template.getEmail(), template.getTeam(), template.getBoat()); CompetitorStore persistentStore2 = new PersistentCompetitorStore(PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(), /* clearStore */ false, null); Competitor competitor2 = persistentStore2.getExistingCompetitorById(template.getId()); @@ -53,14 +53,14 @@ public class CompetitorStoreTest { assertEquals(template.getTeam().getNationality(), competitor2.getTeam().getNationality()); DynamicTeam differentTeam = AbstractLeaderboardTest.createCompetitor("Test Competitor").getTeam(); differentTeam.setNationality(DomainFactory.INSTANCE.getOrCreateNationality("GHA")); // Ghana - Competitor competitor3 = persistentStore2.getOrCreateCompetitor(template.getId(), template.getName(), template.getColor(), differentTeam, template.getBoat()); + Competitor competitor3 = persistentStore2.getOrCreateCompetitor(template.getId(), template.getName(), template.getColor(), template.getEmail(), differentTeam, template.getBoat()); assertSame(competitor2, competitor3); // use existing competitor despite the different team assertNotSame(differentTeam, competitor2.getTeam()); // team expected to remain unchanged assertEquals(competitor.getTeam().getNationality(), competitor3.getTeam().getNationality()); // no updatability requested; nationality expected to remain unchanged // now mark the competitor as to update from defaults persistentStore2.allowCompetitorResetToDefaults(competitor2); - Competitor competitor4 = persistentStore2.getOrCreateCompetitor(template.getId(), template.getName(), template.getColor(), differentTeam, template.getBoat()); + Competitor competitor4 = persistentStore2.getOrCreateCompetitor(template.getId(), template.getName(), template.getColor(), template.getEmail(), differentTeam, template.getBoat()); assertSame(competitor2, competitor4); // expecting an in-place update assertEquals(differentTeam.getNationality(), competitor4.getTeam().getNationality()); } diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/LeaderboardStorageTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/LeaderboardStorageTest.java index df63bd3332f..fa34ead7e9e 100644 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/LeaderboardStorageTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/LeaderboardStorageTest.java @@ -61,7 +61,7 @@ public class LeaderboardStorageTest extends TestCase { DynamicBoat boat = new BoatImpl("woot", service.getBaseDomainFactory().getOrCreateBoatClass("H16"), "70155"); String competitorId = "testC"; Competitor competitor = service.getBaseDomainFactory().getOrCreateCompetitor(competitorId, "Test C", null, - team, boat); + null, team, boat); double carriedPoints = 2.0; leaderboard.setCarriedPoints(competitor, carriedPoints); diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java index 560d365a2c1..bbccc27371c 100755 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java @@ -217,7 +217,7 @@ public class MasterDataImportTest { DynamicTeam team = new TeamImpl("Pros", sailors, coach); BoatClass boatClass = new BoatClassImpl("H16", true); DynamicBoat boat = new BoatImpl("Wingy", boatClass, "GER70133"); - CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, team, boat); + CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, null, team, boat); competitors.add(competitor); UUID competitorToSuppressUUID = UUID.randomUUID(); Set sailors2 = new HashSet(); @@ -227,8 +227,8 @@ public class MasterDataImportTest { "Bester Coach"); DynamicTeam team2 = new TeamImpl("Noobs", sailors2, coach2); DynamicBoat boat2 = new BoatImpl("LahmeEnte", boatClass, "GER1337"); - CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, team2, - boat2); + CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, null, + team2, boat2); competitors.add(competitorToSuppress); RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); TrackedRace trackedRace = new DummyTrackedRace(raceId, competitors, regatta, null, sourceService.getWindStore()); @@ -448,7 +448,7 @@ public class MasterDataImportTest { DynamicTeam team = new TeamImpl("Pros", sailors, coach); BoatClass boatClass = new BoatClassImpl("H16", true); DynamicBoat boat = new BoatImpl("Wingy", boatClass, "GER70133"); - CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, team, boat); + CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, null, team, boat); competitors.add(competitor); UUID competitor2UUID = UUID.randomUUID(); Set sailors2 = new HashSet(); @@ -456,7 +456,7 @@ public class MasterDataImportTest { DynamicPerson coach2 = new PersonImpl("Max Test", new NationalityImpl("GER"), new Date(645487200000L), "desc"); DynamicTeam team2 = new TeamImpl("Pros2", sailors2, coach2); DynamicBoat boat2 = new BoatImpl("FastBoat", boatClass, "GER70133"); - CompetitorImpl competitor2 = new CompetitorImpl(competitor2UUID, "Froderik", Color.RED, team2, boat2); + CompetitorImpl competitor2 = new CompetitorImpl(competitor2UUID, "Froderik", Color.RED, null, team2, boat2); competitors.add(competitor2); RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); TrackedRace trackedRace = new DummyTrackedRace(raceId, competitors, regatta, null, sourceService.getWindStore()); @@ -603,7 +603,7 @@ public class MasterDataImportTest { DynamicTeam team = new TeamImpl("Pros", sailors, coach); BoatClass boatClass = new BoatClassImpl("H16", true); DynamicBoat boat = new BoatImpl("Wingy", boatClass, "GER70133"); - CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, team, boat); + CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, null, team, boat); competitors.add(competitor); RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); @@ -748,7 +748,7 @@ public class MasterDataImportTest { DynamicTeam team = new TeamImpl("Pros", sailors, coach); BoatClass boatClass = new BoatClassImpl("H16", true); DynamicBoat boat = new BoatImpl("Wingy", boatClass, "GER70133"); - Competitor competitor = sourceDomainFactory.getOrCreateCompetitor(competitorUUID, "Froderik", Color.RED, team, + Competitor competitor = sourceDomainFactory.getOrCreateCompetitor(competitorUUID, "Froderik", Color.RED, "noone@nowhere.de", team, boat); competitors.add(competitor); UUID competitor2UUID = UUID.randomUUID(); @@ -758,7 +758,7 @@ public class MasterDataImportTest { DynamicTeam team2 = new TeamImpl("Pros2", sailors2, coach2); DynamicBoat boat2 = new BoatImpl("FastBoat", boatClass, "GER70133"); Competitor competitor2 = sourceDomainFactory.getCompetitorStore().getOrCreateCompetitor(competitor2UUID, - "Froderik", Color.RED, team2, boat2); + "Froderik", Color.RED, "noone@nowhere.de", team2, boat2); competitors.add(competitor2); RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); TrackedRace trackedRace = new DummyTrackedRace(raceId, competitors, regatta, null, sourceService.getWindStore()); @@ -904,7 +904,7 @@ public class MasterDataImportTest { DynamicTeam team = new TeamImpl("Pros", sailors, coach); BoatClass boatClass = new BoatClassImpl("H16", true); DynamicBoat boat = new BoatImpl("Wingy", boatClass, "GER70133"); - CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, team, boat); + CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, null, team, boat); competitors.add(competitor); UUID competitorToSuppressUUID = UUID.randomUUID(); Set sailors2 = new HashSet(); @@ -914,8 +914,8 @@ public class MasterDataImportTest { "Bester Coach"); DynamicTeam team2 = new TeamImpl("Noobs", sailors2, coach2); DynamicBoat boat2 = new BoatImpl("LahmeEnte", boatClass, "GER1337"); - CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, team2, - boat2); + CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, null, + team2, boat2); competitors.add(competitorToSuppress); RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); TrackedRace trackedRace = new DummyTrackedRace(raceId, competitors, regatta, null, sourceService.getWindStore()); @@ -1103,7 +1103,7 @@ public class MasterDataImportTest { DynamicTeam team = new TeamImpl("Pros", sailors, coach); BoatClass boatClass = new BoatClassImpl("H16", true); DynamicBoat boat = new BoatImpl("Wingy", boatClass, "GER70133"); - CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, team, boat); + CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, null, team, boat); competitors.add(competitor); UUID competitorToSuppressUUID = UUID.randomUUID(); Set sailors2 = new HashSet(); @@ -1113,8 +1113,8 @@ public class MasterDataImportTest { "Bester Coach"); DynamicTeam team2 = new TeamImpl("Noobs", sailors2, coach2); DynamicBoat boat2 = new BoatImpl("LahmeEnte", boatClass, "GER1337"); - CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, team2, - boat2); + CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, null, + team2, boat2); competitors.add(competitorToSuppress); RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); TrackedRace trackedRace = new DummyTrackedRace(raceId, competitors, regatta, null, sourceService.getWindStore()); @@ -1205,7 +1205,7 @@ public class MasterDataImportTest { DynamicBoat boatToOverride = new BoatImpl("Wingy", boatClassToOverride, "GER70133"); String competitorOldName = "oldName"; Competitor competitorToOverride = domainFactory.getOrCreateCompetitor(competitorUUID, competitorOldName, - Color.BLUE, teamToOverride, boatToOverride); + Color.BLUE, "noone@nowhere.de", teamToOverride, boatToOverride); competitorsToOverride.add(competitorToOverride); Leaderboard leaderboardToOverride = destService.addRegattaLeaderboard( @@ -1398,7 +1398,7 @@ public class MasterDataImportTest { DynamicTeam team = new TeamImpl("Pros", sailors, coach); BoatClass boatClass = new BoatClassImpl("H16", true); DynamicBoat boat = new BoatImpl("Wingy", boatClass, "GER70133"); - CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, team, boat); + CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, null, team, boat); competitors.add(competitor); UUID competitorToSuppressUUID = UUID.randomUUID(); Set sailors2 = new HashSet(); @@ -1408,8 +1408,8 @@ public class MasterDataImportTest { "Bester Coach"); DynamicTeam team2 = new TeamImpl("Noobs", sailors2, coach2); DynamicBoat boat2 = new BoatImpl("LahmeEnte", boatClass, "GER1337"); - CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, team2, - boat2); + CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, null, + team2, boat2); competitors.add(competitorToSuppress); RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); TrackedRace trackedRace = new DummyTrackedRace(raceId, competitors, regatta, null, sourceService.getWindStore()); @@ -1532,7 +1532,7 @@ public class MasterDataImportTest { DynamicTeam team = new TeamImpl("Pros", sailors, coach); BoatClass boatClass = new BoatClassImpl("H16", true); DynamicBoat boat = new BoatImpl("Wingy", boatClass, "GER70133"); - CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, team, boat); + CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, null, team, boat); competitors.add(competitor); UUID competitorToSuppressUUID = UUID.randomUUID(); Set sailors2 = new HashSet(); @@ -1542,8 +1542,8 @@ public class MasterDataImportTest { "Bester Coach"); DynamicTeam team2 = new TeamImpl("Noobs", sailors2, coach2); DynamicBoat boat2 = new BoatImpl("LahmeEnte", boatClass, "GER1337"); - CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, team2, - boat2); + CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, null, + team2, boat2); competitors.add(competitorToSuppress); RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); TrackedRace trackedRace = new DummyTrackedRace(raceId, competitors, regatta, null, sourceService.getWindStore()); @@ -1748,7 +1748,7 @@ public class MasterDataImportTest { DynamicTeam team = new TeamImpl("Pros", sailors, coach); BoatClass boatClass = new BoatClassImpl("H16", true); DynamicBoat boat = new BoatImpl("Wingy", boatClass, "GER70133"); - CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, team, boat); + CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, null, team, boat); competitors.add(competitor); UUID competitorToSuppressUUID = UUID.randomUUID(); Set sailors2 = new HashSet(); @@ -1758,8 +1758,8 @@ public class MasterDataImportTest { "Bester Coach"); DynamicTeam team2 = new TeamImpl("Noobs", sailors2, coach2); DynamicBoat boat2 = new BoatImpl("LahmeEnte", boatClass, "GER1337"); - CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, team2, - boat2); + CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, null, + team2, boat2); competitors.add(competitorToSuppress); RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); @@ -1977,7 +1977,7 @@ public class MasterDataImportTest { DynamicTeam team = new TeamImpl("Pros", sailors, coach); BoatClass boatClass = new BoatClassImpl("H16", true); DynamicBoat boat = new BoatImpl("Wingy", boatClass, "GER70133"); - CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, team, boat); + CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, null, team, boat); competitors.add(competitor); UUID competitorToSuppressUUID = UUID.randomUUID(); Set sailors2 = new HashSet(); @@ -1987,8 +1987,8 @@ public class MasterDataImportTest { "Bester Coach"); DynamicTeam team2 = new TeamImpl("Noobs", sailors2, coach2); DynamicBoat boat2 = new BoatImpl("LahmeEnte", boatClass, "GER1337"); - CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, team2, - boat2); + CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, null, + team2, boat2); competitors.add(competitorToSuppress); TrackedRace trackedRace = new DummyTrackedRace(raceId, competitors, regatta, null, sourceService.getWindStore()); diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/WindByRaceLogTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/WindByRaceLogTest.java index c400a3a1df3..d9b183abd42 100644 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/WindByRaceLogTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/WindByRaceLogTest.java @@ -105,7 +105,7 @@ public class WindByRaceLogTest { } private Competitor createCompetitor(final DomainFactory masterDomainFactory) { - return masterDomainFactory.getOrCreateCompetitor("GER 61", "Sailor", Color.RED, new TeamImpl("Sailor", + return masterDomainFactory.getOrCreateCompetitor("GER 61", "Sailor", Color.RED, "noone@nowhere.de", new TeamImpl("Sailor", (List) Arrays.asList(new PersonImpl[] { new PersonImpl("Sailor 1", DomainFactory.INSTANCE.getOrCreateNationality("GER"), null, null)}), new PersonImpl("Sailor 2", DomainFactory.INSTANCE.getOrCreateNationality("NED"), null, null)), new BoatImpl("GER 61", DomainFactory.INSTANCE.getOrCreateBoatClass("470", /* typicallyStartsUpwind */ true), "GER 61")); diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy).launch b/java/com.sap.sailing.server/SailingServer (No Proxy).launch index b71d3ef858d..cd7c44d9153 100644 --- a/java/com.sap.sailing.server/SailingServer (No Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy).launch @@ -1,38 +1,38 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch index 895c44256f0..aca684a9833 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch @@ -1,37 +1,37 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch index 4b9fafe0b62..bc1f6674e4a 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch @@ -23,10 +23,10 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch index bff7106c857..7db9e27781e 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch @@ -1,32 +1,32 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch index 0d00afa2ab4..16916c8eb46 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch @@ -1,31 +1,31 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch index 52be241fa8b..06210082415 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch @@ -1,30 +1,30 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch index 0ea337d7c9b..c8df9bb36ef 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch @@ -1,37 +1,37 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy).launch b/java/com.sap.sailing.server/SailingServer (Proxy).launch index 2b681882737..ed447bda52d 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy).launch @@ -1,39 +1,39 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch b/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch index 0d13c3b715a..e8f5ad3d188 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch @@ -1,32 +1,32 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch b/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch index aecc68a6f33..b7ab30bd3dd 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch @@ -1,30 +1,30 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch b/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch index d2372ed03c9..7bb1cdd318d 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch @@ -1,30 +1,30 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch b/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch index 7dac0013df7..a9640ab807e 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch @@ -1,33 +1,33 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/Activator.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/Activator.java index 18fb666575d..fb46cb94e1d 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/Activator.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/Activator.java @@ -73,7 +73,6 @@ public class Activator implements BundleActivator { context.registerService(Replicable.class.getName(), racingEventService, replicableServiceProperties); context.registerService(RacingEventService.class.getName(), racingEventService, null); context.registerService(ClearStateTestSupport.class.getName(), racingEventService, null); - Dictionary properties = new Hashtable(); properties.put(TypeBasedServiceFinder.TYPE, GPSFixImpl.class.getName()); registrations.add(context.registerService(GPSFixMongoHandler.class, diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/PersistentCompetitorStore.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/PersistentCompetitorStore.java index 380907e07af..496bddb79ba 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/PersistentCompetitorStore.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/PersistentCompetitorStore.java @@ -96,9 +96,9 @@ public class PersistentCompetitorStore extends TransientCompetitorStoreImpl impl } @Override - public Competitor updateCompetitor(String idAsString, String newName, Color newRgbDisplayColor, String newSailId, + public Competitor updateCompetitor(String idAsString, String newName, Color newRgbDisplayColor, String newEmail, String newSailId, Nationality newNationality, URI newTeamImageUri) { - Competitor result = super.updateCompetitor(idAsString, newName, newRgbDisplayColor, newSailId, newNationality, + Competitor result = super.updateCompetitor(idAsString, newName, newRgbDisplayColor, newEmail, newSailId, newNationality, newTeamImageUri); storeTo.storeCompetitor(result); return result; diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java index 20d934eeeea..5a9c2791332 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java @@ -60,6 +60,7 @@ import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.DomainFactory; import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.EventBase; +import com.sap.sailing.domain.base.EventFetcher; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.LeaderboardSearchResult; import com.sap.sailing.domain.base.LeaderboardSearchResultBase; @@ -209,7 +210,7 @@ import com.sap.sse.replication.impl.OperationWithResultWithIdWrapper; import com.sap.sse.util.ClearStateTestSupport; public class RacingEventServiceImpl implements RacingEventService, ClearStateTestSupport, RegattaListener, - LeaderboardRegistry, Replicator { + LeaderboardRegistry, Replicator, EventFetcher { private static final Logger logger = Logger.getLogger(RacingEventServiceImpl.class.getName()); /** @@ -462,7 +463,7 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes @Override public void competitorUpdated(Competitor competitor) { replicate(new UpdateCompetitor(competitor.getId().toString(), competitor.getName(), competitor - .getColor(), competitor.getBoat().getSailID(), competitor.getTeam().getNationality(), + .getColor(), competitor.getEmail(), competitor.getBoat().getSailID(), competitor.getTeam().getNationality(), competitor.getTeam().getImage())); } }); @@ -1789,14 +1790,7 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes regatta.addSeries(seriesObj); } } - for (Event event : getAllEvents()) { - event.removeRegatta(regatta); - for (CourseArea courseArea : event.getVenue().getCourseAreas()) { - if (newDefaultCourseAreaId != null && courseArea.getId().equals(newDefaultCourseAreaId)) { - event.addRegatta(regatta); - } - } - } + regatta.adjustEventToRegattaAssociation(this); if (regatta.isPersistent()) { mongoObjectFactory.storeRegatta(regatta); } @@ -2354,7 +2348,7 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes for (Competitor competitor : ((CompetitorStore) ois.readObject()).getCompetitors()) { DynamicCompetitor dynamicCompetitor = (DynamicCompetitor) competitor; competitorStore.getOrCreateCompetitor(dynamicCompetitor.getId(), dynamicCompetitor.getName(), - dynamicCompetitor.getColor(), dynamicCompetitor.getTeam(), dynamicCompetitor.getBoat()); + dynamicCompetitor.getColor(), dynamicCompetitor.getEmail(), dynamicCompetitor.getTeam(), dynamicCompetitor.getBoat()); } logoutput.append("Received " + competitorStore.size() + " NEW competitors\n"); diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateCompetitor.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateCompetitor.java index 3daf9b6f90a..c2e0d7d60ca 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateCompetitor.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateCompetitor.java @@ -14,6 +14,7 @@ public class UpdateCompetitor extends AbstractRacingEventServiceOperationnull, the competitor obtains the "NONE" nationality, usually represented by a white * flag */ - public UpdateCompetitor(String idAsString, String newName, Color newDisplayColor, String newSailId, + public UpdateCompetitor(String idAsString, String newName, Color newDisplayColor, String newEmail, String newSailId, Nationality newNationality, URI newTeamImageUri) { super(); this.idAsString = idAsString; @@ -33,6 +34,7 @@ public class UpdateCompetitor extends AbstractRacingEventServiceOperation + + diff --git a/java/com.sap.sailing.targetplatform.base/plugins/target-base/jersey-multipart-1.17.1.jar b/java/com.sap.sailing.targetplatform.base/plugins/target-base/jersey-multipart-1.17.jar old mode 100644 new mode 100755 similarity index 77% rename from java/com.sap.sailing.targetplatform.base/plugins/target-base/jersey-multipart-1.17.1.jar rename to java/com.sap.sailing.targetplatform.base/plugins/target-base/jersey-multipart-1.17.jar index f469e3948a3..34662e65646 Binary files a/java/com.sap.sailing.targetplatform.base/plugins/target-base/jersey-multipart-1.17.1.jar and b/java/com.sap.sailing.targetplatform.base/plugins/target-base/jersey-multipart-1.17.jar differ diff --git a/java/com.sap.sailing.targetplatform.base/plugins/target-base/org.apache.servicemix.bundles.zxing-3.1.0_1.jar b/java/com.sap.sailing.targetplatform.base/plugins/target-base/org.apache.servicemix.bundles.zxing-3.1.0_1.jar new file mode 100644 index 00000000000..81fd8253c69 Binary files /dev/null and b/java/com.sap.sailing.targetplatform.base/plugins/target-base/org.apache.servicemix.bundles.zxing-3.1.0_1.jar differ diff --git a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target index 21cf5cdef29..e57a3255ffb 100755 --- a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target +++ b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target @@ -1,5 +1,5 @@ - + @@ -47,6 +47,7 @@ + @@ -103,6 +104,7 @@ + @@ -122,6 +124,7 @@ + diff --git a/java/com.sap.sailing.targetplatform/scripts/uploadRepositoryToServer.sh b/java/com.sap.sailing.targetplatform/scripts/uploadRepositoryToServer.sh index ea28968942c..0ef8633fcd8 100755 --- a/java/com.sap.sailing.targetplatform/scripts/uploadRepositoryToServer.sh +++ b/java/com.sap.sailing.targetplatform/scripts/uploadRepositoryToServer.sh @@ -22,7 +22,8 @@ echo "Uploading local repository to $REPO_HOME/sailing" scp -r ../../com.sap.sailing.targetplatform.base/target/repository $USER@$SERVER:$REPO_HOME/sailing echo "Making readable for everyone..." -ssh $USER@$SERVER "chmod -R a+r $REPO_HOME/sailing/*" +ssh $USER@$SERVER "chmod -R a+r $REPO_HOME/sailing" +ssh $USER@$SERVER "chmod -R a+x $REPO_HOME/sailing" echo "Remote files now available:" ssh $USER@$SERVER "ls -lah $REPO_HOME/sailing" diff --git a/java/com.sap.sailing.xrr.structureimport/src/com/sap/sailing/xrr/structureimport/StructureImporter.java b/java/com.sap.sailing.xrr.structureimport/src/com/sap/sailing/xrr/structureimport/StructureImporter.java index c27ee51992d..e183abd1036 100755 --- a/java/com.sap.sailing.xrr.structureimport/src/com/sap/sailing/xrr/structureimport/StructureImporter.java +++ b/java/com.sap.sailing.xrr.structureimport/src/com/sap/sailing/xrr/structureimport/StructureImporter.java @@ -201,11 +201,12 @@ public class StructureImporter { String idAsString = person.getPersonID(); String name = person.getGivenName() + " " + person.getFamilyName(); Color color = null; + String email = null; Nationality nationality = (person.getNOC() == null) ? null : getNationality(person.getNOC() .toString()); BoatAndTeam boatAndTeam = getBoatAndTeam(idAsString, name, nationality, boatClass); this.baseDomainFactory.convertToCompetitorDTO(this.baseDomainFactory.getOrCreateCompetitor( - UUID.fromString(idAsString), name, color, boatAndTeam.getTeam(), boatAndTeam.getBoat())); + UUID.fromString(idAsString), name, color, email, boatAndTeam.getTeam(), boatAndTeam.getBoat())); } else { break; } diff --git a/java/com.sap.sse.common/META-INF/MANIFEST.MF b/java/com.sap.sse.common/META-INF/MANIFEST.MF index 56290d7ee65..22a6396d68e 100644 --- a/java/com.sap.sse.common/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.common/META-INF/MANIFEST.MF @@ -8,7 +8,9 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Export-Package: com.sap.sse.common, com.sap.sse.common.filter, com.sap.sse.common.impl, + com.sap.sse.common.mail, com.sap.sse.common.scalablevalue, - com.sap.sse.common.search + com.sap.sse.common.search, + com.sap.sse.common.util Require-Bundle: com.sap.sse.datamining.shared;bundle-version="1.0.0" Bundle-ActivationPolicy: lazy diff --git a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/MailException.java b/java/com.sap.sse.common/src/com/sap/sse/common/mail/MailException.java similarity index 66% rename from java/com.sap.sse.security.common/src/com/sap/sse/security/shared/MailException.java rename to java/com.sap.sse.common/src/com/sap/sse/common/mail/MailException.java index 9d3491ee00b..a52525a7784 100755 --- a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/MailException.java +++ b/java/com.sap.sse.common/src/com/sap/sse/common/mail/MailException.java @@ -1,4 +1,4 @@ -package com.sap.sse.security.shared; +package com.sap.sse.common.mail; import java.io.Serializable; @@ -10,4 +10,8 @@ public class MailException extends Exception implements Serializable { public MailException(String message) { super(message); } + + public MailException(String message, Throwable caught) { + super(message, caught); + } } diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/util/UrlHelper.java b/java/com.sap.sse.common/src/com/sap/sse/common/util/UrlHelper.java new file mode 100644 index 00000000000..0b55cf0da1c --- /dev/null +++ b/java/com.sap.sse.common/src/com/sap/sse/common/util/UrlHelper.java @@ -0,0 +1,9 @@ +package com.sap.sse.common.util; + +/** + * Context-independent interface for dealing with URLs. + * GWT context does not offer full implementations of URL/URI. * + */ +public interface UrlHelper { + String encodeUrl(String decodedUrlString); +} diff --git a/java/com.sap.sse.datamining.shared/src/com/sap/sse/datamining/shared/impl/GenericGroupKey.java b/java/com.sap.sse.datamining.shared/src/com/sap/sse/datamining/shared/impl/GenericGroupKey.java index 6c04ba3ddb6..305bc07cd25 100644 --- a/java/com.sap.sse.datamining.shared/src/com/sap/sse/datamining/shared/impl/GenericGroupKey.java +++ b/java/com.sap.sse.datamining.shared/src/com/sap/sse/datamining/shared/impl/GenericGroupKey.java @@ -39,7 +39,7 @@ public class GenericGroupKey extends AbstractGroupKey { @Override public String asString() { - return value.toString(); + return value == null ? "null" : value.toString(); } @Override diff --git a/java/com.sap.sse.datamining.test/resources/stringmessages/Test_StringMessages_de.properties b/java/com.sap.sse.datamining.test/resources/stringmessages/Test_StringMessages_de.properties index 0c9f10b0ac6..3adc508bed7 100644 --- a/java/com.sap.sse.datamining.test/resources/stringmessages/Test_StringMessages_de.properties +++ b/java/com.sap.sse.datamining.test/resources/stringmessages/Test_StringMessages_de.properties @@ -10,4 +10,6 @@ LegNumber=Anzahl der Schenkel SailID=Segelnummer TestCluster=Test Cluster Deutsch ClosedBoundariesClusterGroup=Cluster-Gruppe mit geschlossenen Grenzen -OpenBoundariesClusterGroup=Cluster-Gruppe mit offenen Grenzen \ No newline at end of file +OpenBoundariesClusterGroup=Cluster-Gruppe mit offenen Grenzen +increment=Inkrementieren +illegalDimension=Illegale Dimension \ No newline at end of file diff --git a/java/com.sap.sse.datamining.test/resources/stringmessages/Test_StringMessages_en.properties b/java/com.sap.sse.datamining.test/resources/stringmessages/Test_StringMessages_en.properties index 9c4ddd0250b..76b690e6bb9 100644 --- a/java/com.sap.sse.datamining.test/resources/stringmessages/Test_StringMessages_en.properties +++ b/java/com.sap.sse.datamining.test/resources/stringmessages/Test_StringMessages_en.properties @@ -10,4 +10,6 @@ LegNumber=Leg Number SailID=Sail ID TestCluster=Test Cluster English ClosedBoundariesClusterGroup=Cluster group with closed boundaries -OpenBoundariesClusterGroup=Cluster group with open boundaries \ No newline at end of file +OpenBoundariesClusterGroup=Cluster group with open boundaries +increment=Increment +illegalDimension=Illegal Dimension \ No newline at end of file diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestDimensionsValuesQuery.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestDimensionsValuesQuery.java index 7a29b8d2ca9..2a7dc0df8b7 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestDimensionsValuesQuery.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestDimensionsValuesQuery.java @@ -14,11 +14,13 @@ import java.util.concurrent.ExecutionException; import org.junit.Before; import org.junit.Test; +import com.sap.sse.common.Util.Pair; import com.sap.sse.datamining.DataRetrieverChainBuilder; import com.sap.sse.datamining.DataRetrieverChainDefinition; import com.sap.sse.datamining.Query; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.impl.components.GroupedDataEntry; import com.sap.sse.datamining.shared.GroupKey; import com.sap.sse.datamining.shared.dto.FunctionDTO; @@ -71,16 +73,16 @@ public class TestDimensionsValuesQuery { protected Processor, ?> createFirstProcessor() { Processor, Map>> resultCollector = ComponentTestsUtil.getProcessorFactory().createGroupedDataCollectingAsSetProcessor(this); - Collection> legDimensions = new ArrayList<>(); - legDimensions.add(dimensionLegNumber); - legDimensions.add(dimensionCompetitorName); - legDimensions.add(dimensionCompetitorSailID); + Collection, ParameterProvider>> legDimensions = new ArrayList<>(); + legDimensions.add(new Pair<>(dimensionLegNumber, ParameterProvider.NULL)); + legDimensions.add(new Pair<>(dimensionCompetitorName, ParameterProvider.NULL)); + legDimensions.add(new Pair<>(dimensionCompetitorSailID, ParameterProvider.NULL)); - Collection> raceDimensions = new ArrayList<>(); - raceDimensions.add(dimensionRegattaName); - raceDimensions.add(dimensionRaceName); - raceDimensions.add(dimensionBoatClassName); - raceDimensions.add(dimensionYear); + Collection, ParameterProvider>> raceDimensions = new ArrayList<>(); + raceDimensions.add(new Pair<>(dimensionRegattaName, ParameterProvider.NULL)); + raceDimensions.add(new Pair<>(dimensionRaceName, ParameterProvider.NULL)); + raceDimensions.add(new Pair<>(dimensionBoatClassName, ParameterProvider.NULL)); + raceDimensions.add(new Pair<>(dimensionYear, ParameterProvider.NULL)); DataRetrieverChainBuilder> chainBuilder = dataRetrieverChainDefinition.startBuilding(ConcurrencyTestsUtil.getExecutor()); chainBuilder.stepFurther(); //Initialization diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestProcessorQuery.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestProcessorQuery.java index 388c7726d58..de71f3068ee 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestProcessorQuery.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestProcessorQuery.java @@ -20,12 +20,14 @@ import java.util.concurrent.TimeoutException; import org.junit.Before; import org.junit.Test; +import com.sap.sse.common.Util.Pair; import com.sap.sse.datamining.AdditionalResultDataBuilder; import com.sap.sse.datamining.Query; import com.sap.sse.datamining.components.FilterCriterion; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.factories.ProcessorFactory; import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.impl.components.AbstractSimpleParallelProcessor; import com.sap.sse.datamining.impl.components.AbstractSimpleRetrievalProcessor; import com.sap.sse.datamining.impl.components.GroupedDataEntry; @@ -115,11 +117,11 @@ public class TestProcessorQuery { Method getCrossSumMethod = FunctionTestsUtil.getMethodFromClass(Number.class, "getCrossSum"); Function getCrossSumFunction = FunctionTestsUtil.getFunctionFactory().createMethodWrappingFunction(getCrossSumMethod); - Processor, GroupedDataEntry> crossSumExtractor = processorFactory.createExtractionProcessor(sumAggregator, getCrossSumFunction); + Processor, GroupedDataEntry> crossSumExtractor = processorFactory.createExtractionProcessor(sumAggregator, getCrossSumFunction, ParameterProvider.NULL); - List> dimensions = new ArrayList<>(); + List, ParameterProvider>> dimensions = new ArrayList<>(); Function getLengthFunction = FunctionTestsUtil.getFunctionFactory().createMethodWrappingFunction(FunctionTestsUtil.getMethodFromClass(Number.class, "getLength")); - dimensions.add(getLengthFunction); + dimensions.add(new Pair<>(getLengthFunction, ParameterProvider.NULL)); Processor> lengthGrouper = processorFactory.createGroupingProcessor(Number.class, crossSumExtractor, dimensions); Collection> filtrationResultReceivers = new ArrayList<>(); filtrationResultReceivers.add(lengthGrouper); diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestParallelMultiDimensionalGroupingProcessor.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestParallelMultiDimensionalGroupingProcessor.java index 4c7df76280e..042dcd07834 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestParallelMultiDimensionalGroupingProcessor.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestParallelMultiDimensionalGroupingProcessor.java @@ -11,8 +11,11 @@ import java.util.List; import org.junit.Before; import org.junit.Test; +import com.sap.sse.common.Util.Pair; import com.sap.sse.datamining.components.Processor; +import com.sap.sse.datamining.factories.ProcessorFactory; import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.impl.functions.MethodWrappingFunction; import com.sap.sse.datamining.shared.GroupKey; import com.sap.sse.datamining.shared.impl.GenericGroupKey; @@ -25,6 +28,8 @@ import com.sap.sse.datamining.test.util.components.Number; public class TestParallelMultiDimensionalGroupingProcessor { + private final static ProcessorFactory processorFactory = ComponentTestsUtil.getProcessorFactory(); + private Processor> processor; private Collection, ?>> receivers; @@ -43,9 +48,9 @@ public class TestParallelMultiDimensionalGroupingProcessor { receivers = new ArrayList<>(); receivers.add(receiver); - List> dimensions = new ArrayList>(); - dimensions.add(new MethodWrappingFunction<>(Number.class.getMethod("getLength", new Class[0]), int.class)); - dimensions.add(new MethodWrappingFunction<>(Number.class.getMethod("getCrossSum", new Class[0]), int.class)); + List, ParameterProvider>> dimensions = new ArrayList<>(); + dimensions.add(new Pair<>(new MethodWrappingFunction<>(Number.class.getMethod("getLength", new Class[0]), int.class), ParameterProvider.NULL)); + dimensions.add(new Pair<>(new MethodWrappingFunction<>(Number.class.getMethod("getCrossSum", new Class[0]), int.class), ParameterProvider.NULL)); processor = ComponentTestsUtil.getProcessorFactory().createGroupingProcessor(Number.class, receivers, dimensions); } @@ -57,16 +62,16 @@ public class TestParallelMultiDimensionalGroupingProcessor { @Test(expected=IllegalArgumentException.class) public void testConstructionWithEmptyDimensions() { - Iterable> dimensions = new ArrayList<>(); - new ParallelMultiDimensionsValueNestingGroupingProcessor<>(Number.class, ConcurrencyTestsUtil.getExecutor(), receivers, dimensions); + List, ParameterProvider>> dimensions = new ArrayList<>(); + processorFactory.createGroupingProcessor(Number.class, receivers, dimensions); } @Test(expected=IllegalArgumentException.class) public void testConstructionWithFunctionsInsteadOfDimensions() { - Collection> functions = new ArrayList<>(); + List, ParameterProvider>> functions = new ArrayList<>(); Method method = FunctionTestsUtil.getMethodFromSimpleClassWithMarkedMethod("sideEffectFreeValue"); - functions.add(FunctionTestsUtil.getFunctionFactory().createMethodWrappingFunction(method)); - new ParallelMultiDimensionsValueNestingGroupingProcessor<>(Number.class, ConcurrencyTestsUtil.getExecutor(), receivers, functions); + functions.add(new Pair<>(FunctionTestsUtil.getFunctionFactory().createMethodWrappingFunction(method), ParameterProvider.NULL)); + processorFactory.createGroupingProcessor(Number.class, receivers, functions); } @Test diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/criterias/TestFilterCriteria.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/criterias/TestFilterCriteria.java index a1adfcf500d..a5bbcf77bc7 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/criterias/TestFilterCriteria.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/criterias/TestFilterCriteria.java @@ -73,7 +73,7 @@ public class TestFilterCriteria { public void testNullaryFunctionValuesFilterCriterion() { Function getRegattaName = FunctionTestsUtil.getFunctionFactory().createMethodWrappingFunction(FunctionTestsUtil.getMethodFromClass(DataTypeWithContext.class, "getRegattaName")); Collection valuesToMatch = Arrays.asList("Regatta", "Other Regatta"); - FilterCriterion nullaryFunctionFilterCriterion = new NullaryFunctionValuesFilterCriterion<>(DataTypeWithContext.class, getRegattaName, valuesToMatch); + FilterCriterion nullaryFunctionFilterCriterion = new FunctionValuesFilterCriterion<>(DataTypeWithContext.class, getRegattaName, valuesToMatch); DataTypeWithContext regatta = new DataTypeWithContextImpl("Regatta", "Race Name", 7); assertThat(nullaryFunctionFilterCriterion.matches(regatta), is(true)); diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionProvider.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionManagerAsFunctionProvider.java similarity index 71% rename from java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionProvider.java rename to java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionManagerAsFunctionProvider.java index 4910f431c31..e2675a91d79 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionProvider.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionManagerAsFunctionProvider.java @@ -16,8 +16,6 @@ import org.junit.Test; import com.sap.sse.datamining.DataRetrieverChainDefinition; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.functions.Function; -import com.sap.sse.datamining.functions.FunctionProvider; -import com.sap.sse.datamining.functions.FunctionRegistry; import com.sap.sse.datamining.impl.SimpleDataRetrieverChainDefinition; import com.sap.sse.datamining.shared.dto.FunctionDTO; import com.sap.sse.datamining.test.functions.registry.test_classes.Test_Named; @@ -36,56 +34,53 @@ import com.sap.sse.datamining.test.util.components.TestRaceWithContextRetrievalP import com.sap.sse.datamining.test.util.components.TestRegattaRetrievalProcessor; import com.sap.sse.i18n.ResourceBundleStringMessages; -public class TestFunctionProvider { +public class TestFunctionManagerAsFunctionProvider { private static final ResourceBundleStringMessages stringMessages = TestsUtil.getTestStringMessagesWithProductiveMessages(); private ExpectedFunctionRegistryUtil functionRegistryUtil; - private FunctionRegistry functionRegistry; + private FunctionManager functionManager; @Before public void initializeFunctionRegistry() throws NoSuchMethodException, SecurityException { functionRegistryUtil = new ExpectedFunctionRegistryUtil(); - functionRegistry = new SimpleFunctionRegistry(); + functionManager = new FunctionManager(); Collection> internalClassesToScan = new HashSet<>(); internalClassesToScan.add(Test_HasLegOfCompetitorContext.class); internalClassesToScan.add(Test_HasRaceContext.class); - functionRegistry.registerAllWithInternalFunctionPolicy(internalClassesToScan); + functionManager.registerAllWithInternalFunctionPolicy(internalClassesToScan); Collection> externalClassesToScan = new HashSet<>(); externalClassesToScan.add(Test_ExternalLibraryClass.class); - functionRegistry.registerAllWithExternalFunctionPolicy(externalClassesToScan); + functionManager.registerAllWithExternalFunctionPolicy(externalClassesToScan); } @Test public void testGetAllStatistics() { - FunctionProvider functionProvider = new RegistryFunctionProvider(functionRegistry); Collection> expectedFunctions = functionRegistryUtil.getAllExpectedStatistics(); - assertThat(functionProvider.getAllStatistics(), is(expectedFunctions)); + assertThat(functionManager.getAllStatistics(), is(expectedFunctions)); } @Test public void testGetDimensionsForType() { - FunctionProvider functionProvider = new RegistryFunctionProvider(functionRegistry); Collection> expectedDimensions = functionRegistryUtil.getExpectedDimensionsFor(Test_HasRaceContext.class); - assertThat(functionProvider.getDimensionsFor(Test_HasRaceContext.class), is(expectedDimensions)); - assertThat(functionProvider.getDimensionsFor(Test_HasRaceContextImpl.class), is(expectedDimensions)); + assertThat(functionManager.getDimensionsFor(Test_HasRaceContext.class), is(expectedDimensions)); + assertThat(functionManager.getDimensionsFor(Test_HasRaceContextImpl.class), is(expectedDimensions)); expectedDimensions.addAll(functionRegistryUtil.getExpectedDimensionsFor(Test_HasLegOfCompetitorContext.class)); - assertThat(functionProvider.getDimensionsFor(Test_HasLegOfCompetitorContext.class), is(expectedDimensions)); - assertThat(functionProvider.getDimensionsFor(Test_HasLegOfCompetitorContextImpl.class), is(expectedDimensions)); + assertThat(functionManager.getDimensionsFor(Test_HasLegOfCompetitorContext.class), is(expectedDimensions)); + assertThat(functionManager.getDimensionsFor(Test_HasLegOfCompetitorContextImpl.class), is(expectedDimensions)); } @Test public void testGetDimensionsForDataRetrieverChainDefinition() { - FunctionProvider functionProvider = new RegistryFunctionProvider(functionRegistry); DataRetrieverChainDefinition, Test_HasLegOfCompetitorContext> dataRetrieverChainDefinition = createDataRetrieverChainDefinition(); Collection> expectedDimensions = functionRegistryUtil.getExpectedDimensionsFor(Test_HasRaceContext.class); expectedDimensions.addAll(functionRegistryUtil.getExpectedDimensionsFor(Test_HasLegOfCompetitorContext.class)); - assertThat(functionProvider.getDimensionsFor(dataRetrieverChainDefinition), is(expectedDimensions)); + assertThat(functionManager.getDimensionsFor(dataRetrieverChainDefinition), is(expectedDimensions)); } @SuppressWarnings("unchecked") @@ -111,21 +106,18 @@ public class TestFunctionProvider { @Test public void testGetStatisticsForType() { - FunctionProvider functionProvider = new RegistryFunctionProvider(functionRegistry); Collection> expectedStatistics = functionRegistryUtil.getExpectedStatisticsFor(Test_HasLegOfCompetitorContext.class); - assertThat(functionProvider.getStatisticsFor(Test_HasLegOfCompetitorContext.class), is(expectedStatistics)); - assertThat(functionProvider.getStatisticsFor(Test_HasLegOfCompetitorContextImpl.class), is(expectedStatistics)); + assertThat(functionManager.getStatisticsFor(Test_HasLegOfCompetitorContext.class), is(expectedStatistics)); + assertThat(functionManager.getStatisticsFor(Test_HasLegOfCompetitorContextImpl.class), is(expectedStatistics)); } @Test public void testGetAllFunctionsForType() { - FunctionProvider functionProvider = new RegistryFunctionProvider(functionRegistry); - Collection> expectedFunctions = functionRegistryUtil.getExpectedDimensionsFor(Test_HasRaceContext.class); expectedFunctions.addAll(functionRegistryUtil.getExpectedDimensionsFor(Test_HasLegOfCompetitorContext.class)); expectedFunctions.addAll(functionRegistryUtil.getExpectedStatisticsFor(Test_HasLegOfCompetitorContext.class)); - assertThat(functionProvider.getFunctionsFor(Test_HasLegOfCompetitorContext.class), is(expectedFunctions)); - assertThat(functionProvider.getFunctionsFor(Test_HasLegOfCompetitorContextImpl.class), is(expectedFunctions)); + assertThat(functionManager.getFunctionsFor(Test_HasLegOfCompetitorContext.class), is(expectedFunctions)); + assertThat(functionManager.getFunctionsFor(Test_HasLegOfCompetitorContextImpl.class), is(expectedFunctions)); } @Test @@ -138,26 +130,23 @@ public class TestFunctionProvider { FunctionDTO getRegattaNameDTO = FunctionTestsUtil.getFunctionDTOFactory().createFunctionDTO(getRegattaName, stringMessages, Locale.ENGLISH); - FunctionProvider functionProvider = new RegistryFunctionProvider(functionRegistry); @SuppressWarnings("unchecked") // Hamcrest requires type matching of actual and expected type, so the Functions have to be specific (without ) - Function providedFunction = (Function) functionProvider.getFunctionForDTO(getRegattaNameDTO); + Function providedFunction = (Function) functionManager.getFunctionForDTO(getRegattaNameDTO); assertThat(providedFunction, is(getRegattaName)); } @Test public void testGetFunctionForUnregisteredDTO() { - Method incrementMethod = FunctionTestsUtil.getMethodFromClass(SimpleClassWithMarkedMethods.class, "increment", int.class); - Function increment = FunctionTestsUtil.getFunctionFactory().createMethodWrappingFunction(incrementMethod); - FunctionDTO incrementDTO = FunctionTestsUtil.getFunctionDTOFactory().createFunctionDTO(increment, stringMessages, Locale.ENGLISH); + Method illegalDimensionMethod = FunctionTestsUtil.getMethodFromClass(SimpleClassWithMarkedMethods.class, "illegalDimension"); + Function illegalDimension = FunctionTestsUtil.getFunctionFactory().createMethodWrappingFunction(illegalDimensionMethod); + FunctionDTO illegalDimensionDTO = FunctionTestsUtil.getFunctionDTOFactory().createFunctionDTO(illegalDimension, stringMessages, Locale.ENGLISH); - FunctionProvider functionProvider = new RegistryFunctionProvider(functionRegistry); - assertThat(functionProvider.getFunctionForDTO(incrementDTO), is(nullValue())); + assertThat(functionManager.getFunctionForDTO(illegalDimensionDTO), is(nullValue())); } @Test public void testGetFunctionForNullDTO() { - FunctionProvider functionProvider = new RegistryFunctionProvider(functionRegistry); - assertThat(functionProvider.getFunctionForDTO(null), is(nullValue())); + assertThat(functionManager.getFunctionForDTO(null), is(nullValue())); } } diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestSimpleFunctionRegistry.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionManagerAsFunctionRegistry.java similarity index 92% rename from java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestSimpleFunctionRegistry.java rename to java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionManagerAsFunctionRegistry.java index 63a76a5b490..2366eb4935f 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestSimpleFunctionRegistry.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionManagerAsFunctionRegistry.java @@ -12,15 +12,15 @@ import org.junit.BeforeClass; import org.junit.Test; import com.sap.sse.datamining.functions.Function; -import com.sap.sse.datamining.functions.FunctionRegistry; import com.sap.sse.datamining.test.functions.registry.test_contexts.Test_HasContextWithDeadConnectorEnd; import com.sap.sse.datamining.test.functions.registry.test_contexts.Test_HasLegOfCompetitorContext; import com.sap.sse.datamining.test.functions.registry.test_contexts.Test_HasRaceContext; import com.sap.sse.datamining.test.functions.test_classes.Test_ExternalLibraryClass; import com.sap.sse.datamining.test.util.ExpectedFunctionRegistryUtil; +import com.sap.sse.datamining.test.util.OpenFunctionManager; -public class TestSimpleFunctionRegistry { +public class TestFunctionManagerAsFunctionRegistry { private static ExpectedFunctionRegistryUtil expectedFunctionRegistryUtil; @@ -45,7 +45,7 @@ public class TestSimpleFunctionRegistry { @Test public void testRegistration() throws NoSuchMethodException, SecurityException { - FunctionRegistry registry = new SimpleFunctionRegistry(); + OpenFunctionManager registry = new OpenFunctionManager(); registry.registerAllWithInternalFunctionPolicy(internalClassesToScan); registry.registerAllWithExternalFunctionPolicy(externalClassesToScan); @@ -55,7 +55,7 @@ public class TestSimpleFunctionRegistry { assertThat(registry.getDimensions(), is(expectedDimensions)); Collection> expectedStatistics = expectedFunctionRegistryUtil.getExpectedStatisticsFor(Test_HasLegOfCompetitorContext.class); - assertThat(registry.getStatistics(), is(expectedStatistics)); + assertThat(registry.getAllStatistics(), is(expectedStatistics)); Collection> expectedExternalFunctions = expectedFunctionRegistryUtil.getExpectedExternalFunctionsFor(Test_ExternalLibraryClass.class); assertThat(registry.getExternalFunctions(), is(expectedExternalFunctions)); @@ -63,7 +63,7 @@ public class TestSimpleFunctionRegistry { @Test public void testUnregistration() { - FunctionRegistry registry = new SimpleFunctionRegistry(); + OpenFunctionManager registry = new OpenFunctionManager(); registry.registerAllWithInternalFunctionPolicy(internalClassesToScan); Collection> expectedDimensions = new HashSet<>(); diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestMethodWrappingFunctionInvocation.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestMethodWrappingFunctionInvocation.java index 5abb1c7f064..2b8d9647f1b 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestMethodWrappingFunctionInvocation.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestMethodWrappingFunctionInvocation.java @@ -33,14 +33,14 @@ public class TestMethodWrappingFunctionInvocation { @Test public void testInvocationWithParameters() { SimpleClassWithMarkedMethods instance = new SimpleClassWithMarkedMethods(); - int valueToIncrement = 10; - assertThat(increment.tryToInvoke(instance, valueToIncrement), is(instance.increment(valueToIncrement))); + final int valueToIncrement = 10; + assertThat(increment.tryToInvoke(instance, () -> {return new Object[] {valueToIncrement};} ), is(instance.increment(valueToIncrement))); } @Test public void testInvocationWithWrongParameters() { DataTypeWithContext dataEntry = new DataTypeWithContextImpl("Regatta Name", "Race Name", 7); - assertThat(getRegattaName.tryToInvoke(dataEntry, "Wrong Parameter"), is(nullValue())); + assertThat(getRegattaName.tryToInvoke(dataEntry, () -> {return new Object[] {"Wrong Parameter"};} ), is(nullValue())); SimpleClassWithMarkedMethods instance = new SimpleClassWithMarkedMethods(); assertThat(increment.tryToInvoke(instance), is(nullValue())); diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/criterias/TestMethodFilterCriterias.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/criterias/TestMethodFilterCriterias.java index 863c2d887c3..c8a2df4feee 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/criterias/TestMethodFilterCriterias.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/criterias/TestMethodFilterCriterias.java @@ -14,7 +14,7 @@ public class TestMethodFilterCriterias { @Test public void testMethodIsCorrectDimensionFilterCriteria() { - FilterCriterion filterCriteria = new MethodIsCorrectDimensionFilterCriterion(); + FilterCriterion filterCriteria = new MethodIsValidDimensionFilterCriterion(); Method dimension = FunctionTestsUtil.getMethodFromSimpleClassWithMarkedMethod("dimension"); assertThat(filterCriteria.matches(dimension), is(true)); @@ -28,7 +28,7 @@ public class TestMethodFilterCriterias { @Test public void testMethodIsCorrectSideEffectFreeValueFilterCriteria() { - FilterCriterion filterCriteria = new MethodIsCorrectStatisticFilterCriterion(); + FilterCriterion filterCriteria = new MethodIsValidStatisticFilterCriterion(); Method sideEffectFreeValue = FunctionTestsUtil.getMethodFromSimpleClassWithMarkedMethod("sideEffectFreeValue"); assertThat(filterCriteria.matches(sideEffectFreeValue), is(true)); @@ -39,7 +39,7 @@ public class TestMethodFilterCriterias { @Test public void testMethodIsCorrectExternalLibraryFunctionFilterCriteria() { - FilterCriterion filterCriteria = new MethodIsCorrectExternalFunctionFilterCriterion(); + FilterCriterion filterCriteria = new MethodIsValidExternalFunctionFilterCriterion(); Method foo = FunctionTestsUtil.getMethodFromExternalLibraryClass("foo"); assertThat(filterCriteria.matches(foo), is(true)); diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/i18n/TestI18N.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/i18n/TestI18N.java index 6f0ed7cf28a..c806d6ebc24 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/i18n/TestI18N.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/i18n/TestI18N.java @@ -4,8 +4,10 @@ import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; +import java.util.HashSet; import java.util.Locale; import java.util.MissingResourceException; +import java.util.Set; import org.junit.Before; import org.junit.Test; @@ -67,6 +69,20 @@ public class TestI18N { assertThat(ResourceBundleStringMessages.Util.getLocaleFor("Unsupported locale info name"), is(Locale.ENGLISH)); } + @Test + public void testGetSupportedLocales() { + Set supportedLocales = new HashSet<>(); + for (Locale locale : ResourceBundleStringMessages.Util.getSupportedLocales()) { + supportedLocales.add(locale); + } + + Set expectedSupportedLocales = new HashSet<>(); + expectedSupportedLocales.add(Locale.GERMAN); + expectedSupportedLocales.add(Locale.ENGLISH); + + assertThat(supportedLocales, is(expectedSupportedLocales)); + } + @Test(expected=MissingResourceException.class) public void testGettingAMessageForAMissingKeyFromSimpleStringMessages() { testStringMessages.get(Locale.ENGLISH, "$%&MissingKey/()"); diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/functions/test_classes/SimpleClassWithMarkedMethods.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/functions/test_classes/SimpleClassWithMarkedMethods.java index 35cc17cec34..f4ab8dfa163 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/functions/test_classes/SimpleClassWithMarkedMethods.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/functions/test_classes/SimpleClassWithMarkedMethods.java @@ -1,7 +1,10 @@ package com.sap.sse.datamining.test.functions.test_classes; +import java.util.Locale; + import com.sap.sse.datamining.shared.annotations.Dimension; import com.sap.sse.datamining.shared.annotations.Statistic; +import com.sap.sse.i18n.ResourceBundleStringMessages; /* * DON'T CHANGE THE METHOD/CLASS NAMES! @@ -30,8 +33,14 @@ public class SimpleClassWithMarkedMethods { } + //Methods with parameters + public int increment(int i) { return i + 1; } + + public String getLocalizedName(Locale locale, ResourceBundleStringMessages stringMessages) { + return ""; + } } diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/OpenFunctionManager.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/OpenFunctionManager.java new file mode 100644 index 00000000000..1f0070ce588 --- /dev/null +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/OpenFunctionManager.java @@ -0,0 +1,18 @@ +package com.sap.sse.datamining.test.util; + +import java.util.Collection; + +import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.impl.functions.FunctionManager; + +public class OpenFunctionManager extends FunctionManager { + + public Collection> getDimensions() { + return asSet(dimensions); + } + + public Collection> getExternalFunctions() { + return asSet(externalFunctions); + } + +} diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/DataRetrieverChainDefinition.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/DataRetrieverChainDefinition.java index 1d3927e943c..889c4e6a585 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/DataRetrieverChainDefinition.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/DataRetrieverChainDefinition.java @@ -19,16 +19,16 @@ public interface DataRetrieverChainDefinition { public String getLocalizedName(Locale locale, ResourceBundleStringMessages stringMessages); - public void startWith(Class> retrieverType, Class retrievedDataType, String retrievedDataTypeMessageKey); + public void startWith(Class> retrieverType, Class retrievedDataType, String retrievedDataTypeMessageKey); public void - addAfter(Class> previousRetrieverType, - Class> nextRetrieverType, + addAfter(Class> previousRetrieverType, + Class> nextRetrieverType, Class retrievedDataType, String retrievedDataTypeMessageKey); public void - endWith(Class> previousRetrieverType, - Class> lastRetrieverType, + endWith(Class> previousRetrieverType, + Class> lastRetrieverType, Class retrievedDataType, String retrievedDataTypeMessageKey); public List> getDataRetrieverTypesWithInformation(); diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/factories/ProcessorFactory.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/factories/ProcessorFactory.java index 4c867ff80cd..78258b904b7 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/factories/ProcessorFactory.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/factories/ProcessorFactory.java @@ -8,8 +8,10 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; +import com.sap.sse.common.Util.Pair; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.impl.ProcessorQuery; import com.sap.sse.datamining.impl.components.GroupedDataEntry; import com.sap.sse.datamining.impl.components.ParallelByDimensionGroupingProcessor; @@ -75,29 +77,33 @@ public class ProcessorFactory { * Creates an extraction processor with the given aggregation processor as result receiver. */ public Processor, GroupedDataEntry> createExtractionProcessor( - Processor, ?> aggregationProcessor, Function extractionFunction) { + Processor, ?> aggregationProcessor, Function extractionFunction, ParameterProvider parameterProvider) { Collection, ?>> resultReceivers = new ArrayList<>(); resultReceivers.add(aggregationProcessor); - return createExtractionProcessor(resultReceivers, extractionFunction); + return createExtractionProcessor(resultReceivers, extractionFunction, parameterProvider); } public Processor, GroupedDataEntry> createExtractionProcessor( - Collection, ?>> resultReceivers, Function extractionFunction) { + Collection, ?>> resultReceivers, Function extractionFunction, ParameterProvider parameterProvider) { return new ParallelGroupedElementsValueExtractionProcessor(executor, - resultReceivers, extractionFunction); + resultReceivers, extractionFunction, parameterProvider); } /** * Creates a grouping processor with the given extraction processor as result receiver. */ - public Processor> createGroupingProcessor(Class dataType, Processor, ?> extractionProcessor, List> dimensionsToGroupBy) { + public Processor> createGroupingProcessor(Class dataType, Processor, ?> extractionProcessor, Iterable, ParameterProvider>> dimensionsToGroupByWithParameterProvider) { Collection, ?>> resultReceivers = new ArrayList<>(); resultReceivers.add(extractionProcessor); - return createGroupingProcessor(dataType, resultReceivers, dimensionsToGroupBy); + return createGroupingProcessor(dataType, resultReceivers, dimensionsToGroupByWithParameterProvider); } - public Processor> createGroupingProcessor(Class dataType, Collection, ?>> resultReceivers, List> dimensionsToGroupBy) { - return new ParallelMultiDimensionsValueNestingGroupingProcessor<>(dataType, executor, resultReceivers, dimensionsToGroupBy); + public Processor> createGroupingProcessor(Class dataType, Collection, ?>> resultReceivers, Iterable, ParameterProvider>> dimensionsToGroupByWithParameterProvider) { + return new ParallelMultiDimensionsValueNestingGroupingProcessor<>(dataType, executor, resultReceivers, dimensionsToGroupByWithParameterProvider); + } + + public Processor> createGroupingProcessorWithParameter(Class dataType, Collection, ?>> resultReceivers, List, ParameterProvider>> dimensionsToGroupByWithParameterProvider) { + return new ParallelMultiDimensionsValueNestingGroupingProcessor(dataType, executor, resultReceivers, dimensionsToGroupByWithParameterProvider); } /** @@ -105,18 +111,18 @@ public class ProcessorFactory { */ @SuppressWarnings("unchecked") public Collection>> createGroupingExtractorsForDimensions(Class dataType, - Processor, ?> valueCollector, Iterable> dimensions, + Processor, ?> valueCollector, Iterable, ParameterProvider>> dimensionsToGroupByWithParameterProvider, ResourceBundleStringMessages stringMessages, Locale locale) { Collection, ?>> extractionResultReceivers = new ArrayList<>(); extractionResultReceivers.add(valueCollector); Collection>> groupingExtractors = new ArrayList<>(); - for (Function dimension : dimensions) { - Processor, GroupedDataEntry> dimensionValueExtractor = new ParallelGroupedElementsValueExtractionProcessor(executor, extractionResultReceivers, (Function) dimension); + for (Pair, ParameterProvider> dimensionWithParameterProvider : dimensionsToGroupByWithParameterProvider) { + Processor, GroupedDataEntry> dimensionValueExtractor = createExtractionProcessor(extractionResultReceivers, (Function) dimensionWithParameterProvider.getA(), dimensionWithParameterProvider.getB()); Collection, ?>> groupingResultReceivers = new ArrayList<>(); groupingResultReceivers.add(dimensionValueExtractor); - Processor> byDimensionGrouper = new ParallelByDimensionGroupingProcessor<>(dataType, executor, groupingResultReceivers, dimension, stringMessages, locale); + Processor> byDimensionGrouper = new ParallelByDimensionGroupingProcessor<>(dataType, executor, groupingResultReceivers, dimensionWithParameterProvider.getA(), stringMessages, locale); groupingExtractors.add(byDimensionGrouper); } return groupingExtractors; diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/factories/QueryFactory.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/factories/QueryFactory.java index 095f3b50e95..a8af2e4a719 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/factories/QueryFactory.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/factories/QueryFactory.java @@ -3,12 +3,14 @@ package com.sap.sse.datamining.factories; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ExecutorService; +import com.sap.sse.common.Util.Pair; import com.sap.sse.datamining.DataRetrieverChainBuilder; import com.sap.sse.datamining.DataRetrieverChainDefinition; import com.sap.sse.datamining.Query; @@ -16,11 +18,13 @@ import com.sap.sse.datamining.QueryDefinition; import com.sap.sse.datamining.components.FilterCriterion; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.impl.ProcessorQuery; import com.sap.sse.datamining.impl.components.GroupedDataEntry; import com.sap.sse.datamining.impl.criterias.AndCompoundFilterCriterion; import com.sap.sse.datamining.impl.criterias.CompoundFilterCriterion; -import com.sap.sse.datamining.impl.criterias.NullaryFunctionValuesFilterCriterion; +import com.sap.sse.datamining.impl.criterias.FunctionValuesFilterCriterion; +import com.sap.sse.datamining.impl.functions.LocalizationParameterProvider; import com.sap.sse.datamining.shared.GroupKey; import com.sap.sse.i18n.ResourceBundleStringMessages; @@ -37,9 +41,9 @@ public class QueryFactory { Class dataTypeToRetrieve = queryDefinition.getDataType(); Processor, Map> aggregationProcessor = processorFactory.createAggregationProcessor(/*query*/ this, queryDefinition.getAggregatorType(), queryDefinition.getResultType()); - Processor, GroupedDataEntry> extractionProcessor = processorFactory.createExtractionProcessor(aggregationProcessor, extractionFunction); + Processor, GroupedDataEntry> extractionProcessor = processorFactory.createExtractionProcessor(aggregationProcessor, extractionFunction, getParameterProviderFor(extractionFunction, stringMessages, queryDefinition.getLocale())); - Processor> groupingProcessor = processorFactory.createGroupingProcessor(dataTypeToRetrieve, extractionProcessor, queryDefinition.getDimensionsToGroupBy()); + Processor> groupingProcessor = processorFactory.createGroupingProcessor(dataTypeToRetrieve, extractionProcessor, getParameterProvidersFor(queryDefinition.getDimensionsToGroupBy(), stringMessages, queryDefinition.getLocale())); DataRetrieverChainBuilder chainBuilder = queryDefinition.getDataRetrieverChainDefinition().startBuilding(executor); Map> criteriaMappedByRetrieverLevel = createFilterCriteria(queryDefinition.getFilterSelection()); @@ -58,12 +62,45 @@ public class QueryFactory { }; } + private Iterable, ParameterProvider>> getParameterProvidersFor(Iterable> functions, ResourceBundleStringMessages stringMessages, Locale locale) { + Collection, ParameterProvider>> functionsWithParameterProvider = new ArrayList<>(); + for (Function function : functions) { + functionsWithParameterProvider.add(new Pair<>(function, getParameterProviderFor(function, stringMessages, locale))); + } + return functionsWithParameterProvider; + } + + private ParameterProvider getParameterProviderFor(Function function, ResourceBundleStringMessages stringMessages, Locale locale) { + if (functionNeedsLocalizationParameters(function)) { + return new LocalizationParameterProvider(locale, stringMessages); + } + return ParameterProvider.NULL; + } + + private boolean functionNeedsLocalizationParameters(Function function) { + Iterator> parameterTypesIterator = function.getParameters().iterator(); + int parameterCount = 0; + while (parameterTypesIterator.hasNext()) { + parameterCount++; + parameterTypesIterator.next(); + } + if (parameterCount != 2) { + return false; + } + + parameterTypesIterator = function.getParameters().iterator(); + Class firstParameter = parameterTypesIterator.next(); + Class secondParameter = parameterTypesIterator.next(); + return firstParameter.isAssignableFrom(Locale.class) && secondParameter.isAssignableFrom(ResourceBundleStringMessages.class); + } + @SuppressWarnings("unchecked") private Map> createFilterCriteria(Map, Collection>> filterSelection) { Map> criteriaMappedByRetrieverLevel = new HashMap<>(); for (Entry, Collection>> levelFilterSelection : filterSelection.entrySet()) { for (Entry, Collection> levelFilterSelectionEntry : levelFilterSelection.getValue().entrySet()) { Function function = levelFilterSelectionEntry.getKey(); + ParameterProvider parameterProvider = ParameterProvider.NULL; Class dataType = (Class) function.getDeclaringType(); if (!criteriaMappedByRetrieverLevel.containsKey(levelFilterSelection.getKey())) { @@ -71,7 +108,7 @@ public class QueryFactory { } Collection filterValues = new ArrayList<>(levelFilterSelectionEntry.getValue()); - ((CompoundFilterCriterion) criteriaMappedByRetrieverLevel.get(levelFilterSelection.getKey())).addCriteria(new NullaryFunctionValuesFilterCriterion<>(dataType, function, filterValues)); + ((CompoundFilterCriterion) criteriaMappedByRetrieverLevel.get(levelFilterSelection.getKey())).addCriteria(new FunctionValuesFilterCriterion<>(dataType, function, parameterProvider, filterValues)); } } return (Map>)(Map) criteriaMappedByRetrieverLevel; @@ -94,7 +131,7 @@ public class QueryFactory { chainBuilder.stepFurther(); } for (Processor resultReceiver : processorFactory.createGroupingExtractorsForDimensions( - chainBuilder.getCurrentRetrievedDataType(), valueCollector, dimensions, stringMessages, locale)) { + chainBuilder.getCurrentRetrievedDataType(), valueCollector, getParameterProvidersFor(dimensions, stringMessages, locale), stringMessages, locale)) { chainBuilder.addResultReceiver(resultReceiver); } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/Function.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/Function.java index 1159f0a1138..6306c3741c1 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/Function.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/Function.java @@ -20,7 +20,7 @@ public interface Function { * @return true, if the method {@link #getLocalizedName(Locale, ResourceBundleStringMessages) getLocalizedName} would return something * other than the method {@link #getSimpleName()}. */ - public boolean isLocatable(); + public boolean isLocalizable(); public String getLocalizedName(Locale locale, ResourceBundleStringMessages stringMessages); @@ -33,12 +33,13 @@ public interface Function { public ReturnType tryToInvoke(Object instance); /** - * Tries to invoke the function for the given instance and the given parameters. + * Tries to invoke the function for the given instance and the parameters provided + * by the given {@link ParameterProvider}. * * @return The result of the function or null, if an {@link InvocationTargetException}, * {@link IllegalAccessException} or {@link IllegalArgumentException} was thrown. */ - public ReturnType tryToInvoke(Object instance, Object... parameters); + public ReturnType tryToInvoke(Object instance, ParameterProvider parameterProvider); public Unit getResultUnit(); diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionProvider.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionProvider.java index 6a8c3f450b9..1a57fada325 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionProvider.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionProvider.java @@ -6,9 +6,9 @@ import com.sap.sse.datamining.DataRetrieverChainDefinition; import com.sap.sse.datamining.shared.dto.FunctionDTO; public interface FunctionProvider { - - public Collection> getAllStatistics(); + public Collection> getAllStatistics(); + public Collection> getFunctionsFor(Class sourceType); public Collection> getStatisticsFor(Class sourceType); diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionRegistry.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionRegistry.java index 4f0a06a33ec..cc2f40446cd 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionRegistry.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionRegistry.java @@ -1,7 +1,5 @@ package com.sap.sse.datamining.functions; -import java.util.Collection; - public interface FunctionRegistry { public void registerAllWithInternalFunctionPolicy(Iterable> classesToScan); @@ -9,13 +7,4 @@ public interface FunctionRegistry { public void unregisterAllFunctionsOf(Iterable> classesToUnregister); - public Collection> getAllFunctions(); - public Collection> getAllFunctionsOf(Class declaringType); - public Collection> getStatistics(); - public Collection> getStatisticsOf(Class declaringType); - public Collection> getDimensions(); - public Collection> getDimensionsOf(Class declaringType); - public Collection> getExternalFunctions(); - public Collection> getExternalFunctionsOf(Class declaringType); - } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/ParameterProvider.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/ParameterProvider.java new file mode 100644 index 00000000000..7505873159a --- /dev/null +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/ParameterProvider.java @@ -0,0 +1,12 @@ +package com.sap.sse.datamining.functions; + +import com.sap.sse.datamining.impl.functions.NullParameterProvider; + +public interface ParameterProvider { + + public static final ParameterProvider NULL = new NullParameterProvider(); + + public Object[] getParameters(); + + +} diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataMiningActivator.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataMiningActivator.java index 948ee6d3ff5..90e5a38abb9 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataMiningActivator.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataMiningActivator.java @@ -18,10 +18,7 @@ import com.sap.sse.datamining.DataMiningBundleService; import com.sap.sse.datamining.DataMiningServer; import com.sap.sse.datamining.DataRetrieverChainDefinition; import com.sap.sse.datamining.ModifiableDataMiningServer; -import com.sap.sse.datamining.functions.FunctionProvider; -import com.sap.sse.datamining.functions.FunctionRegistry; -import com.sap.sse.datamining.impl.functions.RegistryFunctionProvider; -import com.sap.sse.datamining.impl.functions.SimpleFunctionRegistry; +import com.sap.sse.datamining.impl.functions.FunctionManager; import com.sap.sse.i18n.impl.ResourceBundleStringMessagesImpl; public class DataMiningActivator implements BundleActivator { @@ -40,10 +37,9 @@ public class DataMiningActivator implements BundleActivator { ExecutorService executor = new ThreadPoolExecutor(THREAD_POOL_SIZE, THREAD_POOL_SIZE, 60, TimeUnit.SECONDS, new LinkedBlockingQueue()); - FunctionRegistry functionRegistry = new SimpleFunctionRegistry(); - FunctionProvider functionProvider = new RegistryFunctionProvider(functionRegistry); + FunctionManager functionManager = new FunctionManager(); DataRetrieverChainDefinitionRegistry dataRetrieverChainDefinitionRegistry = new SimpleDataRetrieverChainDefinitionRegistry(); - dataMiningServer = new DataMiningServerImpl(executor, functionRegistry, functionProvider, dataRetrieverChainDefinitionRegistry); + dataMiningServer = new DataMiningServerImpl(executor, functionManager, functionManager, dataRetrieverChainDefinitionRegistry); dataMiningServer.addStringMessages(new ResourceBundleStringMessagesImpl(STRING_MESSAGES_BASE_NAME, DataMiningActivator.class.getClassLoader())); serviceRegistrations = new HashSet<>(); diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataMiningServerImpl.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataMiningServerImpl.java index 9e55eb9742c..f4ed30ff4fb 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataMiningServerImpl.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataMiningServerImpl.java @@ -42,7 +42,7 @@ public class DataMiningServerImpl implements ModifiableDataMiningServer { this.functionProvider = functionProvider; this.dataRetrieverChainDefinitionRegistry = dataRetrieverChainDefinitionRegistry; } - + @Override public ExecutorService getExecutorService() { return executorService; diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataRetrieverTypeWithInformation.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataRetrieverTypeWithInformation.java index 75d94390afd..a6ec6485aa7 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataRetrieverTypeWithInformation.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataRetrieverTypeWithInformation.java @@ -4,11 +4,11 @@ import com.sap.sse.datamining.components.Processor; public class DataRetrieverTypeWithInformation { - private final Class> retrieverType; + private final Class> retrieverType; private final Class retrievedDataType; private final String retrievedDataTypeMessageKey; - public DataRetrieverTypeWithInformation(Class> retrieverType, + public DataRetrieverTypeWithInformation(Class> retrieverType, Class retrievedDataType, String retrievedDataTypeMessageKey) { this.retrieverType = retrieverType; @@ -16,7 +16,7 @@ public class DataRetrieverTypeWithInformation { this.retrievedDataTypeMessageKey = retrievedDataTypeMessageKey; } - public Class> getRetrieverType() { + public Class> getRetrieverType() { return retrieverType; } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/SimpleDataRetrieverChainDefinition.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/SimpleDataRetrieverChainDefinition.java index eea1a097d68..5d0601d37b6 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/SimpleDataRetrieverChainDefinition.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/SimpleDataRetrieverChainDefinition.java @@ -61,7 +61,7 @@ public class SimpleDataRetrieverChainDefinition implem } @Override - public void startWith(Class> retrieverType, + public void startWith(Class> retrieverType, Class retrievedDataType, String retrievedDataTypeMessageKey) { if (isInitialized()) { throw new UnsupportedOperationException("This retriever chain definition already has been started with '" @@ -79,8 +79,8 @@ public class SimpleDataRetrieverChainDefinition implem @Override public void addAfter( - Class> previousRetrieverType, - Class> nextRetrieverType, + Class> previousRetrieverType, + Class> nextRetrieverType, Class retrievedDataType, String retrievedDataTypeMessageKey) { if (!isInitialized()) { throw new UnsupportedOperationException("This retriever chain definition hasn't been started yet"); @@ -103,7 +103,7 @@ public class SimpleDataRetrieverChainDefinition implem } private void checkThatRetrieverHasUsableConstructor( - Class> retrieverType) { + Class> retrieverType) { try { retrieverType.getConstructor(ExecutorService.class, Collection.class); } catch (NoSuchMethodException | SecurityException e) { @@ -114,8 +114,8 @@ public class SimpleDataRetrieverChainDefinition implem @Override public void endWith( - Class> previousRetrieverType, - Class> lastRetrieverType, Class retrievedDataType, + Class> previousRetrieverType, + Class> lastRetrieverType, Class retrievedDataType, String retrievedDataTypeMessageKey) { addAfter(previousRetrieverType, lastRetrieverType, retrievedDataType, retrievedDataTypeMessageKey); isComplete = true; diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractParallelMultiDimensionalNestingGroupingProcessor.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractParallelMultiDimensionalNestingGroupingProcessor.java index 82ba5331753..e08af33291d 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractParallelMultiDimensionalNestingGroupingProcessor.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractParallelMultiDimensionalNestingGroupingProcessor.java @@ -5,37 +5,39 @@ import java.util.Iterator; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; +import com.sap.sse.common.Util.Pair; import com.sap.sse.datamining.AdditionalResultDataBuilder; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.shared.GroupKey; import com.sap.sse.datamining.shared.impl.NestingCompoundGroupKey; public abstract class AbstractParallelMultiDimensionalNestingGroupingProcessor extends AbstractSimpleParallelProcessor> { - private Iterable> dimensions; + private Iterable, ParameterProvider>> dimensionsWithParameterProvider; @SuppressWarnings("unchecked") public AbstractParallelMultiDimensionalNestingGroupingProcessor(Class dataType, ExecutorService executor, Collection, ?>> resultReceivers, - Iterable> dimensions) { + Iterable, ParameterProvider>> dimensionsWithParameterProvider) { super(dataType, (Class>)(Class) GroupedDataEntry.class, executor, resultReceivers); - verifyThatDimensionsAreDimensions(dimensions); - this.dimensions = dimensions; + verifyThatDimensionsAreDimensions(dimensionsWithParameterProvider); + this.dimensionsWithParameterProvider = dimensionsWithParameterProvider; } - private void verifyThatDimensionsAreDimensions(Iterable> dimensions) { - if (dimensions == null) { + private void verifyThatDimensionsAreDimensions(Iterable, ParameterProvider>> dimensionsWithParameterProvider) { + if (dimensionsWithParameterProvider == null) { throw new IllegalArgumentException("The given dimensions mustn't be null."); } int size = 0; - for (Function possibleDimension : dimensions) { + for (Pair, ParameterProvider> possibleDimension : dimensionsWithParameterProvider) { size++; - if (!possibleDimension.isDimension()) { - throw new IllegalArgumentException("The given function " + possibleDimension.toString() + " is no dimension."); + if (!possibleDimension.getA().isDimension()) { + throw new IllegalArgumentException("The given function " + possibleDimension.getA().toString() + " is no dimension."); } } @@ -50,21 +52,21 @@ public abstract class AbstractParallelMultiDimensionalNestingGroupingProcessor call() throws Exception { return new GroupedDataEntry(createCompoundKeyFor(element, - dimensions.iterator()), element); + dimensionsWithParameterProvider.iterator()), element); } }; } - private GroupKey createCompoundKeyFor(DataType input, Iterator> dimensionsIterator) { - Function mainDimension = dimensionsIterator.next(); - GroupKey key = createGroupKeyFor(input, mainDimension); + private GroupKey createCompoundKeyFor(DataType input, Iterator, ParameterProvider>> dimensionsIterator) { + Pair, ParameterProvider> mainDimension = dimensionsIterator.next(); + GroupKey key = createGroupKeyFor(input, mainDimension.getA(), mainDimension.getB()); if (dimensionsIterator.hasNext()) { key = new NestingCompoundGroupKey(key, createCompoundKeyFor(input, dimensionsIterator)); } return key; } - protected abstract GroupKey createGroupKeyFor(DataType input, Function dimension); + protected abstract GroupKey createGroupKeyFor(DataType input, Function dimension, ParameterProvider parameterProvider); @Override protected void setAdditionalData(AdditionalResultDataBuilder additionalDataBuilder) { diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractPartitioningParallelProcessor.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractPartitioningParallelProcessor.java index 8b08c15f117..0b25f006f29 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractPartitioningParallelProcessor.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractPartitioningParallelProcessor.java @@ -5,6 +5,7 @@ import java.util.HashSet; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; +import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; @@ -57,7 +58,16 @@ public abstract class AbstractPartitioningParallelProcessor extends functionDTOFactory = new FunctionDTOFactory(); } - private static Iterable> asIterable(Function dimension) { - Collection> collection = new ArrayList<>(); - collection.add(dimension); + private static Iterable, ParameterProvider>> asIterable(Function dimension) { + Collection, ParameterProvider>> collection = new ArrayList<>(); + collection.add(new Pair, ParameterProvider>(dimension, ParameterProvider.NULL)); return collection; } @Override - protected GroupKey createGroupKeyFor(DataType input, Function dimension) { + protected GroupKey createGroupKeyFor(DataType input, Function dimension, ParameterProvider parameterProvider) { return new GenericGroupKey(functionDTOFactory.createFunctionDTO(dimension, stringMessages, locale)); } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/ParallelGroupedElementsValueExtractionProcessor.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/ParallelGroupedElementsValueExtractionProcessor.java index 0f2be60bbbf..4bb8f96c202 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/ParallelGroupedElementsValueExtractionProcessor.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/ParallelGroupedElementsValueExtractionProcessor.java @@ -7,18 +7,27 @@ import java.util.concurrent.ExecutorService; import com.sap.sse.datamining.AdditionalResultDataBuilder; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.ParameterProvider; public class ParallelGroupedElementsValueExtractionProcessor extends AbstractSimpleParallelProcessor, GroupedDataEntry> { private final Function extractionFunction; + private final ParameterProvider parameterProvider; + + public ParallelGroupedElementsValueExtractionProcessor(ExecutorService executor, + Collection, ?>> resultReceivers, + Function extractionFunction) { + this(executor, resultReceivers, extractionFunction, ParameterProvider.NULL); + } @SuppressWarnings("unchecked") public ParallelGroupedElementsValueExtractionProcessor(ExecutorService executor, Collection, ?>> resultReceivers, - Function extractionFunction) { + Function extractionFunction, ParameterProvider parameterProvider) { super((Class>)(Class) GroupedDataEntry.class, (Class>)(Class) GroupedDataEntry.class, executor, resultReceivers); this.extractionFunction = extractionFunction; + this.parameterProvider = parameterProvider; } @Override @@ -26,7 +35,7 @@ public class ParallelGroupedElementsValueExtractionProcessor>() { @Override public GroupedDataEntry call() throws Exception { - FunctionReturnType value = extractionFunction.tryToInvoke(element.getDataEntry()); + FunctionReturnType value = extractionFunction.tryToInvoke(element.getDataEntry(), parameterProvider); return value != null ? new GroupedDataEntry(element.getKey(), value) : ParallelGroupedElementsValueExtractionProcessor.super.createInvalidResult(); } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/ParallelMultiDimensionsValueNestingGroupingProcessor.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/ParallelMultiDimensionsValueNestingGroupingProcessor.java index 2ae8838b40e..f2e0ab0e641 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/ParallelMultiDimensionsValueNestingGroupingProcessor.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/ParallelMultiDimensionsValueNestingGroupingProcessor.java @@ -3,8 +3,10 @@ package com.sap.sse.datamining.impl.components; import java.util.Collection; import java.util.concurrent.ExecutorService; +import com.sap.sse.common.Util.Pair; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.shared.GroupKey; import com.sap.sse.datamining.shared.impl.GenericGroupKey; @@ -23,12 +25,12 @@ public class ParallelMultiDimensionsValueNestingGroupingProcessor public ParallelMultiDimensionsValueNestingGroupingProcessor(Class dataType, ExecutorService executor, Collection, ?>> resultReceivers, - Iterable> dimensions) { - super(dataType, executor, resultReceivers, dimensions); + Iterable, ParameterProvider>> dimensionsWithParameterProvider) { + super(dataType, executor, resultReceivers, dimensionsWithParameterProvider); } - protected GroupKey createGroupKeyFor(DataType input, Function mainDimension) { - Object keyValue = mainDimension.tryToInvoke(input); + protected GroupKey createGroupKeyFor(DataType input, Function dimension, ParameterProvider parameterProvider) { + Object keyValue = dimension.tryToInvoke(input, parameterProvider); return new GenericGroupKey(keyValue); } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedDoubleDataMedianAggregationProcessor.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedDoubleDataMedianAggregationProcessor.java index f9eced04894..49c2c7a32a3 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedDoubleDataMedianAggregationProcessor.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedDoubleDataMedianAggregationProcessor.java @@ -48,11 +48,11 @@ public class ParallelGroupedDoubleDataMedianAggregationProcessor private Double getMedianOf(List values) { Collections.sort(values); if (listSizeIsEven(values)) { - int index1 = values.size() / 2; + int index1 = (values.size() / 2) - 1; int index2 = index1 + 1; return (values.get(index1) + values.get(index2)) / 2; } else { - int index = (values.size() + 1) / 2; + int index = ((values.size() + 1) / 2) - 1; return values.get(index); } } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/criterias/FunctionValuesFilterCriterion.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/criterias/FunctionValuesFilterCriterion.java new file mode 100644 index 00000000000..7e4531b350f --- /dev/null +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/criterias/FunctionValuesFilterCriterion.java @@ -0,0 +1,39 @@ +package com.sap.sse.datamining.impl.criterias; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Objects; + +import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.ParameterProvider; + +public class FunctionValuesFilterCriterion extends AbstractFilterCriterion { + + private final Function function; + private final ParameterProvider parameterProvider; + private final Collection valuesToMatch; + + public FunctionValuesFilterCriterion(Class elementType, Function function, Collection valuesToMatch) { + this(elementType, function, ParameterProvider.NULL, valuesToMatch); + } + + public FunctionValuesFilterCriterion(Class elementType, Function function, ParameterProvider parameterProvider, Collection valuesToMatch) { + super(elementType); + this.function = function; + this.parameterProvider = parameterProvider; + this.valuesToMatch = new HashSet<>(valuesToMatch); + } + + @Override + public boolean matches(ElementType dataEntry) { + Object value = function.tryToInvoke(dataEntry, parameterProvider); + + for (Object valueToMatch : valuesToMatch) { + if (Objects.equals(value, valueToMatch)) { + return true; + } + } + return false; + } + +} diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/criterias/NullaryFunctionValuesFilterCriterion.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/criterias/NullaryFunctionValuesFilterCriterion.java deleted file mode 100644 index 12ee202edad..00000000000 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/criterias/NullaryFunctionValuesFilterCriterion.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.sap.sse.datamining.impl.criterias; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Objects; - -import com.sap.sse.datamining.functions.Function; - -public class NullaryFunctionValuesFilterCriterion extends AbstractFilterCriterion { - - private Function function; - private Collection valuesToMatch; - - public NullaryFunctionValuesFilterCriterion(Class elementType, Function function, Collection valuesToMatch) { - super(elementType); - this.function = function; - this.valuesToMatch = new HashSet<>(valuesToMatch); - } - - @Override - public boolean matches(ElementType dataEntry) { - Object value = function.tryToInvoke(dataEntry); - - for (Object valueToMatch : valuesToMatch) { - if (Objects.equals(value, valueToMatch)) { - return true; - } - } - return false; - } - -} diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/ConcatenatingCompoundFunction.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/ConcatenatingCompoundFunction.java index 78e469f1b54..295a937a956 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/ConcatenatingCompoundFunction.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/ConcatenatingCompoundFunction.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Locale; import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.shared.Unit; import com.sap.sse.i18n.ResourceBundleStringMessages; @@ -90,7 +91,7 @@ public class ConcatenatingCompoundFunction extends AbstractFunction< if (name != null && !name.isEmpty()) { return name; } - if (!isLocatable()) { + if (!isLocalizable()) { return getSimpleName(); } @@ -98,9 +99,9 @@ public class ConcatenatingCompoundFunction extends AbstractFunction< } @Override - public boolean isLocatable() { + public boolean isLocalizable() { for (Function function : functions) { - if (function.isLocatable()) { + if (function.isLocalizable()) { return true; } } @@ -111,7 +112,7 @@ public class ConcatenatingCompoundFunction extends AbstractFunction< StringBuilder builder = new StringBuilder(); boolean first = true; for (Function function : functions) { - if (function.isLocatable()) { + if (function.isLocalizable()) { if (!first) { builder.append(LOCALIZED_NAME_CHAIN_CONNECTOR); } @@ -124,16 +125,16 @@ public class ConcatenatingCompoundFunction extends AbstractFunction< @Override public ReturnType tryToInvoke(Object instance) { - return tryToInvoke(instance, new Object[0]); + return tryToInvoke(instance, ParameterProvider.NULL); } - - @SuppressWarnings("unchecked") // The cast has to work. The types were checked in the constructor. + + @SuppressWarnings("unchecked") @Override - public ReturnType tryToInvoke(Object instance, Object... parameters) { + public ReturnType tryToInvoke(Object instance, ParameterProvider parameterProvider) { Iterator> functionsIterator = functions.iterator(); - Object result = functionsIterator.next().tryToInvoke(instance, parameters); + Object result = functionsIterator.next().tryToInvoke(instance, parameterProvider); while (functionsIterator.hasNext()) { - Function function = (Function) functionsIterator.next(); + Function function = functionsIterator.next(); result = function.tryToInvoke(result); if (result == null) { return null; diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/FunctionManager.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/FunctionManager.java new file mode 100644 index 00000000000..63aedf5c8ea --- /dev/null +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/FunctionManager.java @@ -0,0 +1,340 @@ +package com.sap.sse.datamining.impl.functions; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.sap.sse.datamining.DataRetrieverChainDefinition; +import com.sap.sse.datamining.components.FilterCriterion; +import com.sap.sse.datamining.factories.FunctionDTOFactory; +import com.sap.sse.datamining.factories.FunctionFactory; +import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.FunctionProvider; +import com.sap.sse.datamining.functions.FunctionRegistry; +import com.sap.sse.datamining.impl.DataRetrieverTypeWithInformation; +import com.sap.sse.datamining.impl.functions.criterias.FunctionMatchesDTOFilterCriterion; +import com.sap.sse.datamining.impl.functions.criterias.MethodIsValidConnectorFilterCriterion; +import com.sap.sse.datamining.impl.functions.criterias.MethodIsValidDimensionFilterCriterion; +import com.sap.sse.datamining.impl.functions.criterias.MethodIsValidExternalFunctionFilterCriterion; +import com.sap.sse.datamining.impl.functions.criterias.MethodIsValidStatisticFilterCriterion; +import com.sap.sse.datamining.shared.dto.FunctionDTO; + +public class FunctionManager implements FunctionRegistry, FunctionProvider { + + private enum FunctionRetrievalStrategies { + All { + @Override + public Collection> retrieveFunctions(Class declaringType, FunctionManager manager) { + return manager.getAllFunctionsOf(declaringType); + } + }, + Dimensions { + @Override + public Collection> retrieveFunctions(Class declaringType, FunctionManager manager) { + return manager.getDimensionsOf(declaringType); + } + }, + Statistics { + @Override + public Collection> retrieveFunctions(Class declaringType, FunctionManager manager) { + return manager.getStatisticsOf(declaringType); + } + }; + + public abstract Collection> retrieveFunctions(Class declaringType, FunctionManager manager); + + } + + private static final Logger LOGGER = Logger.getLogger(FunctionManager.class.getName()); + + private final FilterCriterion isValidDimension = new MethodIsValidDimensionFilterCriterion(); + private final FilterCriterion isValidStatistic = new MethodIsValidStatisticFilterCriterion(); + private final FilterCriterion isValidConnector = new MethodIsValidConnectorFilterCriterion(); + private final FilterCriterion isValidExternalFunction = new MethodIsValidExternalFunctionFilterCriterion(); + + private final FunctionFactory functionFactory; + private final FunctionDTOFactory functionDTOFactory; + + protected final Map, Set>> statistics; + protected final Map, Set>> dimensions; + protected final Map, Set>> externalFunctions; + + private final Collection, Set>>> functionMaps; + + public FunctionManager() { + functionDTOFactory = new FunctionDTOFactory(); + functionFactory = new FunctionFactory(); + + statistics = new HashMap<>(); + dimensions = new HashMap<>(); + externalFunctions = new HashMap<>(); + + functionMaps = new ArrayList<>(); + functionMaps.add(statistics); + functionMaps.add(dimensions); + functionMaps.add(externalFunctions); + } + + @Override + public void registerAllWithInternalFunctionPolicy(Iterable> internalClassesToScan) { + for (Class internalClass : internalClassesToScan) { + scanInternalClass(internalClass); + } + } + + private void scanInternalClass(Class internalClass) { + scanInternalClass(internalClass, new ArrayList>()); + } + + private void scanInternalClass(Class internalClass, List> previousFunctions) { + for (Method method : internalClass.getMethods()) { + if (isValidDimension.matches(method) || isValidStatistic.matches(method)) { + registerFunction(previousFunctions, method); + continue; + } + + if (isValidConnector.matches(method)) { + handleConnectorMethod(method, previousFunctions); + } + } + } + + private void registerFunction(List> previousFunctions, Method method) { + Function function = functionFactory.createMethodWrappingFunction(method); + if (!previousFunctions.isEmpty()) { + function = functionFactory.createCompoundFunction(null, previousFunctions, function); + } + + if (function.isDimension()) { + addDimension(function); + } else { + addStatistic(function); + } + } + + private void addDimension(Function dimension) { + Class declaringType = dimension.getDeclaringType(); + if (!dimensions.containsKey(declaringType)) { + dimensions.put(declaringType, new HashSet>()); + } + dimensions.get(declaringType).add(dimension); + } + + private void addStatistic(Function statistic) { + Class declaringType = statistic.getDeclaringType(); + if (!statistics.containsKey(declaringType)) { + statistics.put(declaringType, new HashSet>()); + } + statistics.get(declaringType).add(statistic); + } + + private void handleConnectorMethod(Method method, List> previousFunctions) { + Function function = functionFactory.createMethodWrappingFunction(method); + Class returnType = method.getReturnType(); + List> previousFunctionsClone = new ArrayList<>(previousFunctions); + previousFunctionsClone.add(function); + scanInternalClass(returnType, previousFunctionsClone); + } + + @Override + public void registerAllWithExternalFunctionPolicy(Iterable> externalClassesToScan) { + for (Class externalClass : externalClassesToScan) { + for (Method method : externalClass.getMethods()) { + if (isValidExternalFunction.matches(method)) { + Function function = functionFactory.createMethodWrappingFunction(method); + addExternalFunction(function); + } + } + } + } + + private void addExternalFunction(Function function) { + Class declaringType = function.getDeclaringType(); + if (!externalFunctions.containsKey(declaringType)) { + externalFunctions.put(declaringType, new HashSet>()); + } + externalFunctions.get(declaringType).add(function); + } + + @Override + public void unregisterAllFunctionsOf(Iterable> classesToUnregister) { + for (Class classToUnregister : classesToUnregister) { + unregisterAllFunctionsOf(classToUnregister); + } + } + + private void unregisterAllFunctionsOf(Class classToUnregister) { + for (Map, Set>> functionMap : functionMaps) { + functionMap.remove(classToUnregister); + } + } + + private Collection> getAllFunctionsOf(Class declaringType) { + Collection> allFunctions = new HashSet<>(); + for (Map, Set>> functionMap : functionMaps) { + Collection> functions = functionMap.get(declaringType); + if (functions != null) { + allFunctions.addAll(functions); + } + } + return allFunctions; + } + + @Override + public Collection> getAllStatistics() { + return asSet(statistics); + } + + private Collection> getStatisticsOf(Class declaringType) { + return statistics.get(declaringType); + } + + private Collection> getDimensionsOf(Class declaringType) { + return dimensions.get(declaringType); + } + + protected Collection> asSet(Map>> map) { + Collection> set = new HashSet<>(); + for (Entry>> entry : map.entrySet()) { + set.addAll(entry.getValue()); + } + return set; + } + + @Override + public Collection> getFunctionsFor(Class sourceType) { + return getFunctionsFor(sourceType, FunctionRetrievalStrategies.All); + } + + @Override + public Collection> getDimensionsFor(Class sourceType) { + return getFunctionsFor(sourceType, FunctionRetrievalStrategies.Dimensions); + } + + @Override + public Collection> getDimensionsFor(DataRetrieverChainDefinition dataRetrieverChainDefinition) { + Collection> dimensions = new HashSet<>(); + for (DataRetrieverTypeWithInformation dataRetrieverTypeWithInformation : dataRetrieverChainDefinition.getDataRetrieverTypesWithInformation()) { + dimensions.addAll(getDimensionsFor(dataRetrieverTypeWithInformation.getRetrievedDataType())); + } + return dimensions; + } + + @Override + public Collection> getStatisticsFor(Class sourceType) { + return getFunctionsFor(sourceType, FunctionRetrievalStrategies.Statistics); + } + + private Collection> getFunctionsFor(Class sourceType, FunctionRetrievalStrategies retrievalStrategy) { + Collection> typesToRetrieve = getSupertypesOf(sourceType); + typesToRetrieve.add(sourceType); + return getFunctionsFor(typesToRetrieve, retrievalStrategy); + } + + private Collection> getFunctionsFor(Collection> typesToRetrieve, FunctionRetrievalStrategies retrievalStrategy) { + Collection> functions = new HashSet<>(); + for (Class typeToRetrieve : typesToRetrieve) { + Collection> typeSpecificFunctions = retrievalStrategy.retrieveFunctions(typeToRetrieve, this); + if (typeSpecificFunctions != null) { + functions.addAll(typeSpecificFunctions); + } + } + return functions; + } + + private Collection> getSupertypesOf(Class type) { + Collection> supertypes = new HashSet<>(); + + supertypes.addAll(getInterfacesOf(type)); + if (isSuperclassValid(type)) { + supertypes.add(type.getSuperclass()); + } + + supertypes.addAll(getSupertypesOf(supertypes)); + return supertypes; + } + + private Collection> getInterfacesOf(Class type) { + return Arrays.asList(type.getInterfaces()); + } + + private boolean isSuperclassValid(Class subType) { + return subType.getSuperclass() != null && !subType.getSuperclass().equals(Object.class); + } + + private Collection> getSupertypesOf(Collection> types) { + Collection> supertypes = new HashSet<>(); + boolean supertypeAdded; + do { + Collection> supertypesToAdd = getSupertypesToAdd(types); + supertypeAdded = supertypes.addAll(supertypesToAdd); + } while (supertypeAdded); + return supertypes; + } + + private Collection> getSupertypesToAdd(Collection> types) { + Collection> supertypesToAdd = new HashSet<>(); + for (Class supertype : types) { + supertypesToAdd.addAll(getSupertypesOf(supertype)); + } + return supertypesToAdd; + } + + @Override + public Function getFunctionForDTO(FunctionDTO functionDTO) { + if (functionDTO == null) { + return null; + } + + Collection> functionsMatchingDTO = getFunctionsForDTO(functionDTO); + if (moreThanOneFunctionMatchedDTO(functionsMatchingDTO)) { + logThatMoreThanOneFunctionMatchedDTO(functionDTO, functionsMatchingDTO); + } + + return getFunctionToReturn(functionsMatchingDTO); + } + + private Collection> getFunctionsForDTO(FunctionDTO functionDTO) { + Collection> functionsMatchingDTO = new HashSet<>(); + FilterCriterion> functionDTOFilterCriteria = new FunctionMatchesDTOFilterCriterion(functionDTOFactory, functionDTO); + for (Function function : getAllFunctions()) { + if (functionDTOFilterCriteria.matches(function)) { + functionsMatchingDTO.add(function); + } + } + return functionsMatchingDTO; + } + + private Collection> getAllFunctions() { + Collection> allFunctions = new HashSet<>(); + for (Map, Set>> functionMap : functionMaps) { + allFunctions.addAll(asSet(functionMap)); + } + return allFunctions; + } + + private boolean moreThanOneFunctionMatchedDTO(Collection> functionsMatchingDTO) { + return functionsMatchingDTO.size() > 1; + } + + private void logThatMoreThanOneFunctionMatchedDTO(FunctionDTO functionDTO, Collection> functionsMatchingDTO) { + LOGGER.log(Level.FINER, "More than on registered function matched the function DTO '" + functionDTO.toString() + "'"); + for (Function function : functionsMatchingDTO) { + LOGGER.log(Level.FINEST, "The function '" + function.toString() + "' matched the function DTO '" + functionDTO.toString() + "'"); + } + } + + private Function getFunctionToReturn(Collection> functionsMatchingDTO) { + return functionsMatchingDTO.isEmpty() ? null : functionsMatchingDTO.iterator().next(); + } + +} diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/LocalizationParameterProvider.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/LocalizationParameterProvider.java new file mode 100644 index 00000000000..edd8f7c0eb5 --- /dev/null +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/LocalizationParameterProvider.java @@ -0,0 +1,13 @@ +package com.sap.sse.datamining.impl.functions; + +import java.util.Locale; + +import com.sap.sse.i18n.ResourceBundleStringMessages; + +public class LocalizationParameterProvider extends SimpleParameterProvider { + + public LocalizationParameterProvider(Locale locale, ResourceBundleStringMessages stringMessages) { + super(new Object[] {locale, stringMessages}); + } + +} diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/MethodWrappingFunction.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/MethodWrappingFunction.java index 77f8a79274d..aef5fa5a74b 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/MethodWrappingFunction.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/MethodWrappingFunction.java @@ -6,6 +6,7 @@ import java.util.Arrays; import java.util.Locale; import java.util.logging.Level; +import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.shared.Unit; import com.sap.sse.datamining.shared.annotations.Connector; import com.sap.sse.datamining.shared.annotations.Dimension; @@ -81,9 +82,13 @@ public class MethodWrappingFunction extends AbstractFunction extends AbstractFunction> retrieveFunctions(Class declaringType, FunctionRegistry registry) { - return registry.getAllFunctionsOf(declaringType); - } - }, - Dimensions { - @Override - public Collection> retrieveFunctions(Class declaringType, FunctionRegistry registry) { - return registry.getDimensionsOf(declaringType); - } - }, - Statistics { - @Override - public Collection> retrieveFunctions(Class declaringType, FunctionRegistry registry) { - return registry.getStatisticsOf(declaringType); - } - }; - - public abstract Collection> retrieveFunctions(Class declaringType, FunctionRegistry registry); - - } - - private static final Logger LOGGER = Logger.getLogger(RegistryFunctionProvider.class.getName()); - - private final FunctionDTOFactory functionDTOFactory; - private final Collection functionRegistries; - - public RegistryFunctionProvider(FunctionRegistry... functionRegistry) { - this(Arrays.asList(functionRegistry)); - } - - public RegistryFunctionProvider(Collection functionRegistries) { - functionDTOFactory = new FunctionDTOFactory(); - this.functionRegistries = new HashSet<>(functionRegistries); - } - - @Override - public Collection> getAllStatistics() { - Collection> allStatistics = new HashSet<>(); - for (FunctionRegistry registry : functionRegistries) { - allStatistics.addAll(registry.getStatistics()); - } - return allStatistics; - } - - @Override - public Collection> getFunctionsFor(Class sourceType) { - return getFunctionsFor(sourceType, FunctionRetrievalStrategies.All); - } - - @Override - public Collection> getDimensionsFor(Class sourceType) { - return getFunctionsFor(sourceType, FunctionRetrievalStrategies.Dimensions); - } - - @Override - public Collection> getDimensionsFor(DataRetrieverChainDefinition dataRetrieverChainDefinition) { - Collection> dimensions = new HashSet<>(); - for (DataRetrieverTypeWithInformation dataRetrieverTypeWithInformation : dataRetrieverChainDefinition.getDataRetrieverTypesWithInformation()) { - dimensions.addAll(getDimensionsFor(dataRetrieverTypeWithInformation.getRetrievedDataType())); - } - return dimensions; - } - - @Override - public Collection> getStatisticsFor(Class sourceType) { - return getFunctionsFor(sourceType, FunctionRetrievalStrategies.Statistics); - } - - private Collection> getFunctionsFor(Class sourceType, FunctionRetrievalStrategies retrievalStrategy) { - Collection> typesToRetrieve = getSupertypesOf(sourceType); - typesToRetrieve.add(sourceType); - return getFunctionsFor(typesToRetrieve, retrievalStrategy); - } - - private Collection> getFunctionsFor(Collection> typesToRetrieve, FunctionRetrievalStrategies retrievalStrategy) { - Collection> functions = new HashSet<>(); - for (FunctionRegistry functionRegistry : functionRegistries) { - for (Class typeToRetrieve : typesToRetrieve) { - Collection> typeSpecificFunctions = retrievalStrategy.retrieveFunctions(typeToRetrieve, functionRegistry); - if (typeSpecificFunctions != null) { - functions.addAll(typeSpecificFunctions); - } - } - } - return functions; - } - - private Collection> getSupertypesOf(Class type) { - Collection> supertypes = new HashSet<>(); - - supertypes.addAll(getInterfacesOf(type)); - if (isSuperclassValid(type)) { - supertypes.add(type.getSuperclass()); - } - - supertypes.addAll(getSupertypesOf(supertypes)); - return supertypes; - } - - private Collection> getInterfacesOf(Class type) { - return Arrays.asList(type.getInterfaces()); - } - - private boolean isSuperclassValid(Class subType) { - return subType.getSuperclass() != null && !subType.getSuperclass().equals(Object.class); - } - - private Collection> getSupertypesOf(Collection> types) { - Collection> supertypes = new HashSet<>(); - boolean supertypeAdded; - do { - Collection> supertypesToAdd = getSupertypesToAdd(types); - supertypeAdded = supertypes.addAll(supertypesToAdd); - } while (supertypeAdded); - return supertypes; - } - - private Collection> getSupertypesToAdd(Collection> types) { - Collection> supertypesToAdd = new HashSet<>(); - for (Class supertype : types) { - supertypesToAdd.addAll(getSupertypesOf(supertype)); - } - return supertypesToAdd; - } - - @Override - public Function getFunctionForDTO(FunctionDTO functionDTO) { - if (functionDTO == null) { - return null; - } - - Collection> functionsMatchingDTO = getFunctionsForDTO(functionDTO); - if (moreThanOneFunctionMatchedDTO(functionsMatchingDTO)) { - logThatMoreThanOneFunctionMatchedDTO(functionDTO, functionsMatchingDTO); - } - - return getFunctionToReturn(functionsMatchingDTO); - } - - private Collection> getFunctionsForDTO(FunctionDTO functionDTO) { - Collection> functionsMatchingDTO = new HashSet<>(); - FilterCriterion> functionDTOFilterCriteria = new FunctionMatchesDTOFilterCriterion(functionDTOFactory, functionDTO); - for (FunctionRegistry functionRegistry : functionRegistries) { - for (Function function : functionRegistry.getAllFunctions()) { - if (functionDTOFilterCriteria.matches(function)) { - functionsMatchingDTO.add(function); - } - } - } - return functionsMatchingDTO; - } - - private boolean moreThanOneFunctionMatchedDTO(Collection> functionsMatchingDTO) { - return functionsMatchingDTO.size() > 1; - } - - private void logThatMoreThanOneFunctionMatchedDTO(FunctionDTO functionDTO, Collection> functionsMatchingDTO) { - LOGGER.log(Level.FINER, "More than on registered function matched the function DTO '" + functionDTO.toString() + "'"); - for (Function function : functionsMatchingDTO) { - LOGGER.log(Level.FINEST, "The function '" + function.toString() + "' matched the function DTO '" + functionDTO.toString() + "'"); - } - } - - private Function getFunctionToReturn(Collection> functionsMatchingDTO) { - return functionsMatchingDTO.isEmpty() ? null : functionsMatchingDTO.iterator().next(); - } - -} diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/SimpleFunctionRegistry.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/SimpleFunctionRegistry.java deleted file mode 100644 index 0a96a0b8c13..00000000000 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/SimpleFunctionRegistry.java +++ /dev/null @@ -1,218 +0,0 @@ -package com.sap.sse.datamining.impl.functions; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - -import com.sap.sse.datamining.factories.FunctionFactory; -import com.sap.sse.datamining.functions.Function; -import com.sap.sse.datamining.functions.FunctionRegistry; -import com.sap.sse.datamining.shared.annotations.Connector; -import com.sap.sse.datamining.shared.annotations.Dimension; -import com.sap.sse.datamining.shared.annotations.Statistic; - -public class SimpleFunctionRegistry implements FunctionRegistry { - - private final FunctionFactory functionFactory; - - private final Map, Set>> statistics; - private final Map, Set>> dimensions; - private final Map, Set>> externalFunctions; - - private final Collection, Set>>> functionMaps; - - public SimpleFunctionRegistry() { - functionFactory = new FunctionFactory(); - - statistics = new HashMap<>(); - dimensions = new HashMap<>(); - externalFunctions = new HashMap<>(); - - functionMaps = new ArrayList<>(); - functionMaps.add(statistics); - functionMaps.add(dimensions); - functionMaps.add(externalFunctions); - } - - @Override - public void registerAllWithInternalFunctionPolicy(Iterable> internalClassesToScan) { - for (Class internalClass : internalClassesToScan) { - scanInternalClass(internalClass); - } - } - - private void scanInternalClass(Class internalClass) { - scanInternalClass(internalClass, new ArrayList>()); - } - - private void scanInternalClass(Class internalClass, List> previousFunctions) { - for (Method method : internalClass.getMethods()) { - if (isValidDimension(method) || isValidStatistic(method)) { - registerFunction(previousFunctions, method); - continue; - } - - if (isConnector(method)) { - handleConnectorMethod(method, previousFunctions); - } - } - } - - private void registerFunction(List> previousFunctions, Method method) { - Function function = functionFactory.createMethodWrappingFunction(method); - if (!previousFunctions.isEmpty()) { - function = functionFactory.createCompoundFunction(null, previousFunctions, function); - } - - if (function.isDimension()) { - addDimension(function); - } else { - addStatistic(function); - } - } - - private void addDimension(Function dimension) { - Class declaringType = dimension.getDeclaringType(); - if (!dimensions.containsKey(declaringType)) { - dimensions.put(declaringType, new HashSet>()); - } - dimensions.get(declaringType).add(dimension); - } - - private void addStatistic(Function statistic) { - Class declaringType = statistic.getDeclaringType(); - if (!statistics.containsKey(declaringType)) { - statistics.put(declaringType, new HashSet>()); - } - statistics.get(declaringType).add(statistic); - } - - private void handleConnectorMethod(Method method, List> previousFunctions) { - Function function = functionFactory.createMethodWrappingFunction(method); - Class returnType = method.getReturnType(); - List> previousFunctionsClone = new ArrayList<>(previousFunctions); - previousFunctionsClone.add(function); - scanInternalClass(returnType, previousFunctionsClone); - } - - private boolean isValidDimension(Method method) { - return method.getAnnotation(Dimension.class) != null && - !method.getReturnType().equals(Void.TYPE) && - method.getParameterTypes().length == 0; - } - - private boolean isValidStatistic(Method method) { - return method.getAnnotation(Statistic.class) != null && - !method.getReturnType().equals(Void.TYPE) && - method.getParameterTypes().length == 0; - } - - private boolean isConnector(Method method) { - return method.getAnnotation(Connector.class) != null && - !method.getReturnType().equals(Void.TYPE) && - method.getParameterTypes().length == 0; - } - - @Override - public void registerAllWithExternalFunctionPolicy(Iterable> externalClassesToScan) { - for (Class externalClass : externalClassesToScan) { - for (Method method : externalClass.getMethods()) { - if (isValidExternalFunction(method)) { - Function function = functionFactory.createMethodWrappingFunction(method); - addExternalFunction(function); - } - } - } - } - - private void addExternalFunction(Function function) { - Class declaringType = function.getDeclaringType(); - if (!externalFunctions.containsKey(declaringType)) { - externalFunctions.put(declaringType, new HashSet>()); - } - externalFunctions.get(declaringType).add(function); - } - - private boolean isValidExternalFunction(Method method) { - return !method.getReturnType().equals(Void.TYPE) && !method.getDeclaringClass().equals(Object.class); - } - - @Override - public void unregisterAllFunctionsOf(Iterable> classesToUnregister) { - for (Class classToUnregister : classesToUnregister) { - unregisterAllFunctionsOf(classToUnregister); - } - } - - private void unregisterAllFunctionsOf(Class classToUnregister) { - for (Map, Set>> functionMap : functionMaps) { - functionMap.remove(classToUnregister); - } - } - - @Override - public Collection> getAllFunctions() { - Collection> allFunctions = new HashSet<>(); - for (Map, Set>> functionMap : functionMaps) { - allFunctions.addAll(asSet(functionMap)); - } - return allFunctions; - } - - @Override - public Collection> getAllFunctionsOf(Class declaringType) { - Collection> allFunctions = new HashSet<>(); - for (Map, Set>> functionMap : functionMaps) { - Collection> functions = functionMap.get(declaringType); - if (functions != null) { - allFunctions.addAll(functions); - } - } - return allFunctions; - } - - @Override - public Collection> getStatistics() { - return asSet(statistics); - } - - @Override - public Collection> getStatisticsOf(Class declaringType) { - return statistics.get(declaringType); - } - - @Override - public Collection> getDimensions() { - return asSet(dimensions); - } - - @Override - public Collection> getDimensionsOf(Class declaringType) { - return dimensions.get(declaringType); - } - - @Override - public Collection> getExternalFunctions() { - return asSet(externalFunctions); - } - - @Override - public Collection> getExternalFunctionsOf(Class declaringType) { - return externalFunctions.get(declaringType); - } - - private Collection> asSet(Map>> map) { - Collection> set = new HashSet<>(); - for (Entry>> entry : map.entrySet()) { - set.addAll(entry.getValue()); - } - return set; - } - -} diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/SimpleParameterProvider.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/SimpleParameterProvider.java new file mode 100644 index 00000000000..0717107c18c --- /dev/null +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/SimpleParameterProvider.java @@ -0,0 +1,18 @@ +package com.sap.sse.datamining.impl.functions; + +import com.sap.sse.datamining.functions.ParameterProvider; + +public class SimpleParameterProvider implements ParameterProvider { + + private final Object[] parameters; + + public SimpleParameterProvider(Object[] parameters) { + this.parameters = parameters; + } + + @Override + public Object[] getParameters() { + return parameters; + } + +} diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/IsDeclaringTypeFilterCriterion.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/IsDeclaringTypeFilterCriterion.java index adfdc8d6060..4e95830b116 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/IsDeclaringTypeFilterCriterion.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/IsDeclaringTypeFilterCriterion.java @@ -53,7 +53,7 @@ public class IsDeclaringTypeFilterCriterion extends AbstractFunctionFilterCriter return false; } - public boolean isDeclaringTypeMatching(Function function, Class expectingType) { + private boolean isDeclaringTypeMatching(Function function, Class expectingType) { return function.getDeclaringType().equals(expectingType); } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsValidConnectorFilterCriterion.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsValidConnectorFilterCriterion.java new file mode 100644 index 00000000000..852866da46a --- /dev/null +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsValidConnectorFilterCriterion.java @@ -0,0 +1,16 @@ +package com.sap.sse.datamining.impl.functions.criterias; + +import java.lang.reflect.Method; + +import com.sap.sse.datamining.shared.annotations.Connector; + +public class MethodIsValidConnectorFilterCriterion extends AbstractMethodFilterCriterion { + + @Override + public boolean matches(Method method) { + return method.getAnnotation(Connector.class) != null && + !method.getReturnType().equals(Void.TYPE) && + method.getParameterTypes().length == 0; + } + +} diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsCorrectDimensionFilterCriterion.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsValidDimensionFilterCriterion.java similarity index 78% rename from java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsCorrectDimensionFilterCriterion.java rename to java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsValidDimensionFilterCriterion.java index f3c054f4f8a..380cde12d29 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsCorrectDimensionFilterCriterion.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsValidDimensionFilterCriterion.java @@ -4,7 +4,7 @@ import java.lang.reflect.Method; import com.sap.sse.datamining.shared.annotations.Dimension; -public class MethodIsCorrectDimensionFilterCriterion extends AbstractMethodFilterCriterion { +public class MethodIsValidDimensionFilterCriterion extends AbstractMethodFilterCriterion { @Override public boolean matches(Method method) { diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsCorrectExternalFunctionFilterCriterion.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsValidExternalFunctionFilterCriterion.java similarity index 72% rename from java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsCorrectExternalFunctionFilterCriterion.java rename to java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsValidExternalFunctionFilterCriterion.java index af7923a68d8..239507361e3 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsCorrectExternalFunctionFilterCriterion.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsValidExternalFunctionFilterCriterion.java @@ -2,7 +2,7 @@ package com.sap.sse.datamining.impl.functions.criterias; import java.lang.reflect.Method; -public class MethodIsCorrectExternalFunctionFilterCriterion extends AbstractMethodFilterCriterion { +public class MethodIsValidExternalFunctionFilterCriterion extends AbstractMethodFilterCriterion { @Override public boolean matches(Method method) { diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsCorrectStatisticFilterCriterion.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsValidStatisticFilterCriterion.java similarity index 50% rename from java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsCorrectStatisticFilterCriterion.java rename to java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsValidStatisticFilterCriterion.java index 9223e3acc65..88cfece2c6a 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsCorrectStatisticFilterCriterion.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsValidStatisticFilterCriterion.java @@ -4,11 +4,12 @@ import java.lang.reflect.Method; import com.sap.sse.datamining.shared.annotations.Statistic; -public class MethodIsCorrectStatisticFilterCriterion extends AbstractMethodFilterCriterion { +public class MethodIsValidStatisticFilterCriterion extends AbstractMethodFilterCriterion { @Override public boolean matches(Method method) { - return method.getAnnotation(Statistic.class) != null; + return method.getAnnotation(Statistic.class) != null && + !method.getReturnType().equals(Void.TYPE); } } diff --git a/java/com.sap.sse.filestorage.test/src/com/sap/sse/filestorage/replication/test/AbstractFileStorageManagementServiceReplicationTest.java b/java/com.sap.sse.filestorage.test/src/com/sap/sse/filestorage/replication/test/AbstractFileStorageManagementServiceReplicationTest.java index be1aba6fc9a..b8e02563ed2 100755 --- a/java/com.sap.sse.filestorage.test/src/com/sap/sse/filestorage/replication/test/AbstractFileStorageManagementServiceReplicationTest.java +++ b/java/com.sap.sse.filestorage.test/src/com/sap/sse/filestorage/replication/test/AbstractFileStorageManagementServiceReplicationTest.java @@ -10,43 +10,49 @@ import com.sap.sse.filestorage.impl.EmptyFileStorageServicePropertyStoreImpl; import com.sap.sse.filestorage.impl.FileStorageManagementServiceImpl; import com.sap.sse.filestorage.testsupport.DummyFileStorageService; import com.sap.sse.mongodb.MongoDBService; -import com.sap.sse.replication.testsupport.AbstractServerReplicationTest; +import com.sap.sse.replication.testsupport.AbstractServerWithSingleServiceReplicationTest; public abstract class AbstractFileStorageManagementServiceReplicationTest extends - AbstractServerReplicationTest { - private MongoDBService mongoDBService; + AbstractServerWithSingleServiceReplicationTest { + public AbstractFileStorageManagementServiceReplicationTest() { + super(new ServerReplicationTestSetUp()); + } - @Override - protected void persistenceSetUp(boolean dropDB) { - mongoDBService = MongoDBService.INSTANCE; - if (dropDB) { + private static class ServerReplicationTestSetUp extends com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp { + private MongoDBService mongoDBService; + + @Override + protected void persistenceSetUp(boolean dropDB) { + mongoDBService = MongoDBService.INSTANCE; + if (dropDB) { + mongoDBService.getDB().dropDatabase(); + } + } + + protected FileStorageManagementServiceImpl createNewService() { + return new FileStorageManagementServiceImpl( + new SingleTypeBasedServiceFinderImpl<>(new DummyFileStorageService(), + DummyFileStorageService.NAME), EmptyFileStorageServicePropertyStoreImpl.INSTANCE); + } + + @Override + public FileStorageManagementServiceImpl createNewMaster() throws MalformedURLException, IOException, + InterruptedException { + FileStorageManagementServiceImpl result = createNewService(); + FileStorageService serviceOnMaster = result.getAvailableFileStorageServices()[0]; + result.setActiveFileStorageService(serviceOnMaster); + result.setFileStorageServiceProperty(serviceOnMaster, DummyFileStorageService.PROPERTY_NAME, "123"); + return result; + } + + @Override + public FileStorageManagementServiceImpl createNewReplica() { + return createNewService(); + } + + @Override + protected void persistenceTearDown() { mongoDBService.getDB().dropDatabase(); } } - - protected FileStorageManagementServiceImpl createNewService() { - return new FileStorageManagementServiceImpl( - new SingleTypeBasedServiceFinderImpl<>(new DummyFileStorageService(), - DummyFileStorageService.NAME), EmptyFileStorageServicePropertyStoreImpl.INSTANCE); - } - - @Override - protected FileStorageManagementServiceImpl createNewMaster() throws MalformedURLException, IOException, - InterruptedException { - FileStorageManagementServiceImpl result = createNewService(); - FileStorageService serviceOnMaster = result.getAvailableFileStorageServices()[0]; - result.setActiveFileStorageService(serviceOnMaster); - result.setFileStorageServiceProperty(serviceOnMaster, DummyFileStorageService.PROPERTY_NAME, "123"); - return result; - } - - @Override - protected FileStorageManagementServiceImpl createNewReplica() { - return createNewService(); - } - - @Override - protected void persistenceTearDown() { - mongoDBService.getDB().dropDatabase(); - } } diff --git a/java/com.sap.sse.filestorage.test/src/com/sap/sse/filestorage/services/test/AmazonS3Test.java b/java/com.sap.sse.filestorage.test/src/com/sap/sse/filestorage/services/test/AmazonS3Test.java index 5d96698b4c9..0550a90b196 100644 --- a/java/com.sap.sse.filestorage.test/src/com/sap/sse/filestorage/services/test/AmazonS3Test.java +++ b/java/com.sap.sse.filestorage.test/src/com/sap/sse/filestorage/services/test/AmazonS3Test.java @@ -1,5 +1,7 @@ package com.sap.sse.filestorage.services.test; +import static org.junit.Assert.assertTrue; + import java.io.IOException; import java.io.InputStream; import java.net.URI; @@ -36,8 +38,10 @@ public class AmazonS3Test { InputStream downloadStream = uri.toURL().openStream(); stream = getClass().getClassLoader().getResourceAsStream(teamImageFile); - IOUtils.contentEquals(downloadStream, stream); - - storageService.removeFile(uri); + try { + assertTrue(IOUtils.contentEquals(downloadStream, stream)); + } finally { + storageService.removeFile(uri); + } } } diff --git a/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/FileStorageService.java b/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/FileStorageService.java index 9aa9f27b545..7c2668e7f75 100644 --- a/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/FileStorageService.java +++ b/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/FileStorageService.java @@ -34,7 +34,7 @@ public interface FileStorageService extends IsManagedByCache + * A panel that can be used to implement an administration console. Widgets can be arranged in vertical and horizontal + * tabs ("L-shape"). The top-level element is the vertical tab panel. Widgets may either be added directly as the + * content of one vertical tab, or a horizontal tab panel can be added as the content widget of a vertical tab, in turn + * holding widgets in horizontal tabs. + *

    * - * After constructing an instance of this class, there are three ways for adding widgets:

      - *
    • {@link #addToVerticalTabPanel(RefreshableAdminConsolePanel, String, AdminConsoleFeatures)} adds a widget as a content element - * of a vertical tab
    • - *
    • {@link #addVerticalTab(String, String, AdminConsoleFeatures)} creates a horizontal tab panel and adds it as a content element of - * a vertical tab
    • - *
    • {@link #addToTabPanel(TabLayoutPanel, RefreshableAdminConsolePanel, String, AdminConsoleFeatures)} adds a widget as a content element - * of a horizontal tab
    • + * After constructing an instance of this class, there are three ways for adding widgets: + *
        + *
      • {@link #addToVerticalTabPanel(RefreshableAdminConsolePanel, String, Permission)} adds a widget as a content + * element of a vertical tab
      • + *
      • {@link #addVerticalTab(String, String, Permission)} creates a horizontal tab panel and adds it as a content + * element of a vertical tab
      • + *
      • {@link #addToTabPanel(TabLayoutPanel, RefreshableAdminConsolePanel, String, Permission)} adds a widget as a + * content element of a horizontal tab
      • *
      * - * Widgets to be added need to be wrapped as {@link RefreshableAdminConsolePanel} holding the widget and receiving the refresh call when - * the widget is shown because the user has selected the tab. If the component doesn't require any refresh logic, an instance of - * {@link DefaultRefreshableAdminConsolePanel} can be used to wrap the widget.

      + * Widgets to be added need to be wrapped as {@link RefreshableAdminConsolePanel} holding the widget and receiving the + * refresh call when the widget is shown because the user has selected the tab. If the component doesn't require any + * refresh logic, an instance of {@link DefaultRefreshableAdminConsolePanel} can be used to wrap the widget. + *

      * - * After the widgets have been added, {@link #initUI()} must be called to assemble all tabs for the current user's roles. The {@link #initUI()} - * method must be called each time more widgets have been added dynamically.

      + * After the widgets have been added, {@link #initUI()} must be called to assemble all tabs for the current user's + * roles. The {@link #initUI()} method must be called each time more widgets have been added dynamically. + *

      * - * For each widget added, a {@link AdminConsoleFeatures feature} needs to be specified. The feature tells the roles of which the logged-in user - * needs to have at least one in order to see the tab. When the user changes or has his/her roles updated the set of tabs visible will be - * adjusted according to the new roles available for the logged-in user.

      + * For each widget added, a {@link Permission set of permissions} needs to be specified, any of which is sufficient to + * get to see the widget. When the user changes or has his/her permissions updated the set of tabs visible will be + * adjusted according to the new roles available for the logged-in user. + *

      * * @author Axel Uhl (D043530) * @@ -59,10 +71,14 @@ public class AdminConsolePanel extends DockLayoutPanel { /** * The administration console's UI depends on the user's roles. When the roles change then so shall the display of - * tabs. {@link AdminConsoleFeatures} list the roles to which they are made available. This map keeps track of the - * dependencies and allows the UI to adjust to role changes. + * tabs. Required {@link Permission}s tell when they are to be made available based on the user's actual + * permissions. This map keeps track of the dependencies and allows the UI to adjust to role changes.

      + * + * The values are the permissions, at least one of which is required from the user to be able to see the widget. */ - private final LinkedHashMap, AdminConsoleFeatures> roleSpecificTabs; + private final LinkedHashSet> roleSpecificTabs; + + private final Map> permissionsAnyOfWhichIsRequiredToSeeWidget; private final SelectionHandler tabSelectionHandler; @@ -79,6 +95,8 @@ public class AdminConsolePanel extends DockLayoutPanel { */ private final Map panelsByWidget; + private final PermissionsForRoleProvider permissionsForRoleProvider; + /** * Generic selection handler that forwards selected tabs to a refresher that ensures that data gets reloaded. If * you add a new tab then make sure to have a look at #refreshDataFor(Widget widget) to ensure that upon @@ -130,12 +148,14 @@ public class AdminConsolePanel extends DockLayoutPanel { } } - public AdminConsolePanel(UserService userService, BuildVersionRetriever buildVersionRetriever, - String releaseNotesAnchorLabel, String releaseNotesURL, - ErrorReporter errorReporter, LoginPanelCss loginPanelCss) { + public AdminConsolePanel(UserService userService, PermissionsForRoleProvider permissionsForRoleProvider, + BuildVersionRetriever buildVersionRetriever, String releaseNotesAnchorLabel, + String releaseNotesURL, ErrorReporter errorReporter, LoginPanelCss loginPanelCss) { super(Unit.EM); + this.permissionsForRoleProvider = permissionsForRoleProvider; + this.permissionsAnyOfWhichIsRequiredToSeeWidget = new HashMap<>(); this.userService = userService; - roleSpecificTabs = new LinkedHashMap<>(); + roleSpecificTabs = new LinkedHashSet<>(); this.panelsByWidget = new HashMap<>(); getUserService().addUserStatusEventHandler(new UserStatusEventHandler() { @Override @@ -158,6 +178,11 @@ public class AdminConsolePanel extends DockLayoutPanel { public void remove(Widget child) { topLevelTabPanel.remove(child); } + + @Override + public Widget getPanel() { + return topLevelTabPanel; + } }; final DockPanel informationPanel = new DockPanel(); informationPanel.setSize("100%", "95%"); @@ -197,6 +222,8 @@ public class AdminConsolePanel extends DockLayoutPanel { void add(Widget child, String text, boolean asHtml); void remove(Widget child); + + Widget getPanel(); } /** @@ -205,12 +232,12 @@ public class AdminConsolePanel extends DockLayoutPanel { * @return the horizontal tab panel that was created and added to the top-level vertical tab panel; the panel returned can be specified * as argument to {@link #addToTabPanel(TabLayoutPanel, Widget, String, AdminConsoleFeatures)}. */ - public TabLayoutPanel addVerticalTab(String tabTitle, String tabDebugId, AdminConsoleFeatures feature) { + public TabLayoutPanel addVerticalTab(String tabTitle, String tabDebugId, Permission... requiresAnyOfThesePermissions) { final TabLayoutPanel newTabPanel = new TabLayoutPanel(2.5, Unit.EM); AbstractEntryPoint.setTabPanelSize(newTabPanel, "100%", "100%"); newTabPanel.addSelectionHandler(tabSelectionHandler); newTabPanel.ensureDebugId(tabDebugId); - remeberWidgetLocationAndFeature(topLevelTabPanelWrapper, newTabPanel, tabTitle, feature); + remeberWidgetLocationAndPermissions(topLevelTabPanelWrapper, newTabPanel, tabTitle, requiresAnyOfThesePermissions); return newTabPanel; } @@ -219,8 +246,8 @@ public class AdminConsolePanel extends DockLayoutPanel { * This is useful for panels that form a top-level category of its own but don't require multiple panels to represent this * top-level category. */ - public void addToVerticalTabPanel(final RefreshableAdminConsolePanel panelToAdd, String tabTitle, AdminConsoleFeatures feature) { - addToTabPanel(topLevelTabPanelWrapper, panelToAdd, tabTitle, feature); + public void addToVerticalTabPanel(final RefreshableAdminConsolePanel panelToAdd, String tabTitle, Permission... requiresAnyOfThesePermissions) { + addToTabPanel(topLevelTabPanelWrapper, panelToAdd, tabTitle, requiresAnyOfThesePermissions); } private ScrollPanel wrapInScrollPanel(Widget panelToAdd) { @@ -230,7 +257,7 @@ public class AdminConsolePanel extends DockLayoutPanel { return scrollPanel; } - public void addToTabPanel(final TabLayoutPanel tabPanel, RefreshableAdminConsolePanel panelToAdd, String tabTitle, AdminConsoleFeatures feature) { + public void addToTabPanel(final TabLayoutPanel tabPanel, RefreshableAdminConsolePanel panelToAdd, String tabTitle, Permission... requiresAnyOfThesePermissions) { VerticalOrHorizontalTabLayoutPanel wrapper = new VerticalOrHorizontalTabLayoutPanel() { @Override public void add(Widget child, String text, boolean asHtml) { @@ -242,8 +269,13 @@ public class AdminConsolePanel extends DockLayoutPanel { public void remove(Widget child) { tabPanel.remove(child); } + + @Override + public Widget getPanel() { + return tabPanel; + } }; - addToTabPanel(wrapper, panelToAdd, tabTitle, feature); + addToTabPanel(wrapper, panelToAdd, tabTitle, requiresAnyOfThesePermissions); } /** @@ -251,18 +283,34 @@ public class AdminConsolePanel extends DockLayoutPanel { * a hook so that when the panelToAdd's widget is selected then the {@link RefreshableAdminConsolePanel#refreshAfterBecomingVisible()} * method can be called. */ - private void addToTabPanel(VerticalOrHorizontalTabLayoutPanel tabPanel, RefreshableAdminConsolePanel panelToAdd, String tabTitle, AdminConsoleFeatures feature) { - remeberWidgetLocationAndFeature(tabPanel, wrapInScrollPanel(panelToAdd.getWidget()), tabTitle, feature); + private void addToTabPanel(VerticalOrHorizontalTabLayoutPanel tabPanel, RefreshableAdminConsolePanel panelToAdd, String tabTitle, Permission... requiresAnyOfThesePermissions) { + remeberWidgetLocationAndPermissions(tabPanel, wrapInScrollPanel(panelToAdd.getWidget()), tabTitle, requiresAnyOfThesePermissions); panelsByWidget.put(panelToAdd.getWidget(), panelToAdd); } /** - * Remembers the tab panel in which the widgetToAdd is to be displayed and for which feature. + * Remembers the tab panel in which the widgetToAdd is to be displayed and which permissions are + * sufficient to see the widget. For the tabPanel, all permissions provided here are added to the tab + * panel's permissions so that the user will see the tab panel as soon as the user may see any of the widgets inside + * that panel + * + * @param requiresAnyOfThesePermissions + * zero or more permissions; if no permissions are provided, the user will never be able to see the + * widget. Otherwise, if any of these permissions implies any of the permissions the user has, the user + * will be shown the widget. In particular, a "*" wildcard permission will show the widget to all users, + * regardless their actual permissions. */ - private void remeberWidgetLocationAndFeature(VerticalOrHorizontalTabLayoutPanel tabPanel, Widget widgetToAdd, - String tabTitle, AdminConsoleFeatures feature) { - roleSpecificTabs.put(new Triple(tabPanel, - widgetToAdd, tabTitle), feature); + private void remeberWidgetLocationAndPermissions(VerticalOrHorizontalTabLayoutPanel tabPanel, Widget widgetToAdd, + String tabTitle, Permission... requiresAnyOfThesePermissions) { + roleSpecificTabs.add(new Triple(tabPanel, widgetToAdd, tabTitle)); + final HashSet permissionsAsSet = new HashSet<>(Arrays.asList(requiresAnyOfThesePermissions)); + permissionsAnyOfWhichIsRequiredToSeeWidget.put(widgetToAdd, permissionsAsSet); + Set permissionsForTabPanel = permissionsAnyOfWhichIsRequiredToSeeWidget.get(tabPanel.getPanel()); + if (permissionsForTabPanel == null) { + permissionsForTabPanel = new HashSet<>(); + permissionsAnyOfWhichIsRequiredToSeeWidget.put(tabPanel.getPanel(), permissionsForTabPanel); + } + permissionsForTabPanel.addAll(permissionsAsSet); } /** @@ -270,26 +318,37 @@ public class AdminConsolePanel extends DockLayoutPanel { * to see which tabs. See {@link #roleSpecificTabs}. */ private void updateTabDisplayForCurrentUser(UserDTO user) { - for (Map.Entry, AdminConsoleFeatures> e : roleSpecificTabs - .entrySet()) { - final Widget panelToAdd = e.getKey().getB(); - if (user != null && isUserInRole(e.getValue().getEnabledRoles())) { - e.getKey().getA().add(panelToAdd, e.getKey().getC(), /* asHtml */false); + for (Triple e : roleSpecificTabs) { + final Widget widgetToAdd = e.getB(); + if (user != null && userHasPermissionsToSeeWidget(user, e.getB())) { + e.getA().add(widgetToAdd, e.getC(), /* asHtml */false); } else { - e.getKey().getA().remove(panelToAdd); + e.getA().remove(widgetToAdd); } } } - private boolean isUserInRole(String... roles) { - boolean result = false; - UserDTO user = getUserService().getCurrentUser(); - for (String enabledRole : roles) { - if (user.hasRole(enabledRole)) { - result = true; - break; + /** + * A user is defined to have permission to see a widget if the widget's required permissions imply any of the permissions the + * user has. This may at first seem the wrong way around. However, the problem is that a widget cannot express a general permission + * that is implied by any detailed permissions. Wildcard permissions don't work this way. Instead, a wildcard permission implies + * detailed permissions. This way, if the widget requires, say, "event:*:*" (or "event" for short), this permission implies + * all more detailed event permissions such as "event:write:9456192873". Therefore, the permissions provided for widgets + * must imply a permission the user has in order for the user to see the tab. More detailed permissions checks can then be + * applied at a more detailed level of the UI and, of course, in the back end. Additionally, if any of the user's permissions + * implies the required permission (e.g., the user having "*" as the administrator's permission), permission to see the widget + * is also implied. + */ + private boolean userHasPermissionsToSeeWidget(UserDTO user, Widget widget) { + for (Permission requiredStringPermission : permissionsAnyOfWhichIsRequiredToSeeWidget.get(widget)) { + WildcardPermission requiredPermission = new WildcardPermission(requiredStringPermission.getStringPermission()); + for (String userStringPermission : user.getAllPermissions(permissionsForRoleProvider)) { + WildcardPermission userPermission = new WildcardPermission(userStringPermission); + if (requiredPermission.implies(userPermission) || userPermission.implies(requiredPermission)) { + return true; + } } } - return result; + return false; } } diff --git a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/StringMessages.java b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/StringMessages.java new file mode 100755 index 00000000000..27138ce5644 --- /dev/null +++ b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/StringMessages.java @@ -0,0 +1,15 @@ +package com.sap.sse.gwt.adminconsole; + +import com.google.gwt.i18n.client.Messages; + +public interface StringMessages extends Messages { + String upload(); + + String remove(); + + String removeResult(String status, String message); + + String uploadSuccessful(); + + String fileUploadResult(String status, String message); +} diff --git a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java new file mode 100755 index 00000000000..579a9424548 --- /dev/null +++ b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java @@ -0,0 +1,116 @@ +package com.sap.sse.gwt.adminconsole; + +import com.google.gwt.dom.client.InputElement; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.json.client.JSONArray; +import com.google.gwt.json.client.JSONObject; +import com.google.gwt.json.client.JSONParser; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.FileUpload; +import com.google.gwt.user.client.ui.FormPanel; +import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteEvent; +import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteHandler; +import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.Image; +import com.google.gwt.user.client.ui.PushButton; +import com.google.gwt.user.client.ui.SubmitButton; +import com.google.gwt.user.client.ui.TextBox; + +/** + * A {@link TextBox} together with an upload button that can use the file storage service to + * save the file and produce a URL for it which is then taken over into the text field. + * + * @author Axel Uhl (d043530) + * + */ +public class URLFieldWithFileUpload extends HorizontalPanel { + private final TextBox urlTextBox; + + private final FileUpload fileUploadField; + + private String uri; + + private final PushButton removeButton; + + public URLFieldWithFileUpload(final StringMessages stringMessages) { + final FormPanel removePanel = new FormPanel(); + removePanel.addSubmitCompleteHandler(new SubmitCompleteHandler() { + @Override + public void onSubmitComplete(SubmitCompleteEvent event) { + final JSONObject resultJson = JSONParser.parseLenient(event.getResults().replaceFirst("]*>(.*)", "$1")).isObject(); + Window.alert(stringMessages.removeResult(resultJson.get("status").isString().stringValue(), + resultJson.get("message") == null ? "" : resultJson.get("message").isString().stringValue())); + setURL(""); + } + }); + removePanel.setMethod(FormPanel.METHOD_POST); + removeButton = new PushButton(new Image(com.sap.sse.gwt.client.IconResources.INSTANCE.removeIcon())); + removeButton.setEnabled(false); // the button shall only be enabled as long as we know the URI for removal + removeButton.ensureDebugId("RemoveButton"); + removeButton.setTitle(stringMessages.remove()); + removeButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + removePanel.setAction("/sailingserver/api/v1/file?uri="+uri); + removePanel.submit(); + uri = null; + } + }); + removePanel.add(removeButton); + urlTextBox = new TextBox(); + add(urlTextBox); + FormPanel formPanel = new FormPanel(); + HorizontalPanel formPanelContent = new HorizontalPanel(); + formPanel.add(formPanelContent); + formPanel.setAction("/sailingserver/fileupload"); + formPanel.setEncoding(FormPanel.ENCODING_MULTIPART); + formPanel.setMethod(FormPanel.METHOD_POST); + fileUploadField = new FileUpload(); + formPanelContent.add(fileUploadField); + final InputElement inputElement = fileUploadField.getElement().cast(); + inputElement.setName("file"); + SubmitButton submitButton = new SubmitButton(stringMessages.upload()); + formPanelContent.add(submitButton); + add(formPanel); + formPanel.addSubmitCompleteHandler(new SubmitCompleteHandler() { + @Override + public void onSubmitComplete(SubmitCompleteEvent event) { + String result = event.getResults(); + JSONArray resultJson = (JSONArray) JSONParser.parseLenient(result.replaceFirst("]*>(.*)", "$1")); + if (resultJson != null) { + if (resultJson.get(0).isObject().get("file_uri") != null) { + uri = resultJson.get(0).isObject().get("file_uri").isString().stringValue(); + urlTextBox.setValue(uri); + removeButton.setEnabled(true); + Window.alert(stringMessages.uploadSuccessful()); + } else { + Window.alert(stringMessages.fileUploadResult(resultJson.get(0).isObject().get("status").isString().stringValue(), + resultJson.get(0).isObject().get("message").isString().stringValue())); + } + } + } + }); + add(removePanel); + } + + /** + * Returns null if the trimmed URL field contents are empty + */ + public String getURL() { + final String trimmedUrl = urlTextBox.getValue().trim(); + return trimmedUrl.isEmpty() ? null : trimmedUrl; + } + + public void setURL(String imageURL) { + if (imageURL == null) { + urlTextBox.setValue(""); + uri = null; + removeButton.setEnabled(false); + } else { + urlTextBox.setValue(imageURL); + uri = imageURL; + removeButton.setEnabled(true); + } + } +} diff --git a/java/com.sap.sse.gwt.test/META-INF/MANIFEST.MF b/java/com.sap.sse.gwt.test/META-INF/MANIFEST.MF index d62d8cf0d7b..4986d296d89 100644 --- a/java/com.sap.sse.gwt.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.gwt.test/META-INF/MANIFEST.MF @@ -5,5 +5,7 @@ Bundle-SymbolicName: com.sap.sse.gwt.test Bundle-Version: 1.0.0.qualifier Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.7 -Require-Bundle: org.junit4;bundle-version="4.8.2" -Fragment-Host: com.sap.sse.gwt;bundle-version="1.0.0.qualifier" +Require-Bundle: com.sap.sse.gwt;bundle-version="1.0.0", + com.sap.sse.common;bundle-version="1.0.0", + org.junit4;bundle-version="4.8.2", + com.google.gwt.osgi;bundle-version="2.7.0" diff --git a/java/com.sap.sse.gwt.test/build.properties b/java/com.sap.sse.gwt.test/build.properties index f11210d3f2c..0a97af7a804 100644 --- a/java/com.sap.sse.gwt.test/build.properties +++ b/java/com.sap.sse.gwt.test/build.properties @@ -2,4 +2,5 @@ source.. = src/,\ resources/ output.. = bin/ bin.includes = META-INF/,\ - . + .,\ + resources/ diff --git a/java/com.sap.sse.gwt.test/pom.xml b/java/com.sap.sse.gwt.test/pom.xml index 2d43815d42e..73933277341 100644 --- a/java/com.sap.sse.gwt.test/pom.xml +++ b/java/com.sap.sse.gwt.test/pom.xml @@ -9,18 +9,34 @@ com.sap.sse.gwt.test eclipse-test-plugin - + + + com.google.gwt + gwt-dev + ${gwt.version} + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/AsyncActionsExecutorTest.java + + + org.codehaus.mojo gwt-maven-plugin - 2.4.0 - - **/AsyncActionsExecutorTest.java - + ${gwt.version} + + **/AsyncActionsExecutorTest.java + test diff --git a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/AsyncActionsExecutorTest.java b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/AsyncActionsExecutorTest.java index e8779f89c46..121b468cfe9 100644 --- a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/AsyncActionsExecutorTest.java +++ b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/AsyncActionsExecutorTest.java @@ -55,7 +55,16 @@ public class AsyncActionsExecutorTest extends GWTTestCase { }; private AsyncActionsExecutor executor; - + + /** + * Delegate to super; this has the effect of putting this class on the stack frame before the module loading takes place. + * Therefore, this class's class loader will be consulted for resolving the GWT module definition. + */ + @Override + public void runTest() { + super.run(); + } + @Test public void testThatANonTriggeringActionLeadsToPendingCall() { executor = new AsyncActionsExecutor(/*maxPendingCalls*/6, /*maxPendingCallsPerType*/5, diff --git a/java/com.sap.sse.gwt.theme/.classpath b/java/com.sap.sse.gwt.theme/.classpath new file mode 100644 index 00000000000..ffa9f4e615c --- /dev/null +++ b/java/com.sap.sse.gwt.theme/.classpath @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/java/com.sap.sse.gwt.theme/.gitignore b/java/com.sap.sse.gwt.theme/.gitignore new file mode 100644 index 00000000000..62f0b377a8a --- /dev/null +++ b/java/com.sap.sse.gwt.theme/.gitignore @@ -0,0 +1,6 @@ +/bin +/.gwt +/.generated +/gwt-unitCache +/extras +/com.sap.sse.gwt.theme.* diff --git a/java/com.sap.sse.gwt.theme/.project b/java/com.sap.sse.gwt.theme/.project new file mode 100644 index 00000000000..8e679e93a05 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/.project @@ -0,0 +1,39 @@ + + + com.sap.sse.gwt.theme + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + com.google.gdt.eclipse.core.webAppProjectValidator + + + + + com.google.gwt.eclipse.core.gwtProjectValidator + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + com.google.gwt.eclipse.core.gwtNature + + diff --git a/java/com.sap.sse.gwt.theme/.settings/com.google.gwt.eclipse.core.prefs b/java/com.sap.sse.gwt.theme/.settings/com.google.gwt.eclipse.core.prefs new file mode 100644 index 00000000000..e992774d99b --- /dev/null +++ b/java/com.sap.sse.gwt.theme/.settings/com.google.gwt.eclipse.core.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgSU5GT11dPjwvZXh0cmEtYXJncz48dm0tYXJncz48IVtDREFUQVstWG14NTEybV1dPjwvdm0tYXJncz48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc3NlLmd3dC50aGVtZS5TU0VUaGVtZTwvZW50cnktcG9pbnQtbW9kdWxlPjwvZ3d0LWNvbXBpbGUtc2V0dGluZ3M+ diff --git a/java/com.sap.sse.gwt.theme/META-INF/MANIFEST.MF b/java/com.sap.sse.gwt.theme/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..b94daa9dc2a --- /dev/null +++ b/java/com.sap.sse.gwt.theme/META-INF/MANIFEST.MF @@ -0,0 +1,17 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: SSE Base Theme +Bundle-SymbolicName: com.sap.sse.gwt.theme +Bundle-Version: 1.0.0.qualifier +Bundle-Vendor: SAP +Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Bundle-ActivationPolicy: lazy +Bundle-ClassPath: WEB-INF/classes/ +Import-Package: org.osgi.framework;version="1.7.0", + javax.servlet;version="3.1.0", + javax.servlet.descriptor;version="3.1.0", + javax.servlet.http;version="3.1.0" +Require-Bundle: com.google.gwt.osgi;bundle-version="2.7.0", + com.sap.sse.common +Web-ContextPath: /theme +Export-Package: com.sap.sse.gwt.theme.client diff --git a/java/com.sap.sse.gwt.theme/SSETheme.css b/java/com.sap.sse.gwt.theme/SSETheme.css new file mode 100644 index 00000000000..9a49fb97ef1 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/SSETheme.css @@ -0,0 +1,3 @@ +.mainPanel { + +} \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/SSETheme.html b/java/com.sap.sse.gwt.theme/SSETheme.html new file mode 100644 index 00000000000..cb45c7aad46 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/SSETheme.html @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + SAP Sports Engine Theme + + + + + + + + + + + + + + + + + + + + + + + + +

      + Your web browser must have JavaScript enabled + in order for this application to display correctly. +
      + + + + diff --git a/java/com.sap.sse.gwt.theme/SSETheme_GWT sdm.launch b/java/com.sap.sse.gwt.theme/SSETheme_GWT sdm.launch new file mode 100644 index 00000000000..71c80a9b61b --- /dev/null +++ b/java/com.sap.sse.gwt.theme/SSETheme_GWT sdm.launch @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sse.gwt.theme/SSETheme_GWT.launch b/java/com.sap.sse.gwt.theme/SSETheme_GWT.launch new file mode 100644 index 00000000000..099018ea63b --- /dev/null +++ b/java/com.sap.sse.gwt.theme/SSETheme_GWT.launch @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sse.gwt.theme/WEB-INF/.gitignore b/java/com.sap.sse.gwt.theme/WEB-INF/.gitignore new file mode 100644 index 00000000000..46b0c4dbcf4 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/WEB-INF/.gitignore @@ -0,0 +1,2 @@ +deploy +classes diff --git a/java/com.sap.sse.gwt.theme/WEB-INF/classes/.dummy b/java/com.sap.sse.gwt.theme/WEB-INF/classes/.dummy new file mode 100755 index 00000000000..e69de29bb2d diff --git a/java/com.sap.sse.gwt.theme/WEB-INF/web.xml b/java/com.sap.sse.gwt.theme/WEB-INF/web.xml new file mode 100644 index 00000000000..af72bde0bba --- /dev/null +++ b/java/com.sap.sse.gwt.theme/WEB-INF/web.xml @@ -0,0 +1,25 @@ + + + + + + default + org.eclipse.jetty.servlet.DefaultServlet + + + dirAllowed + false + + + useFileMappedBuffer + false + + + + + SSETheme.html + + diff --git a/java/com.sap.sse.gwt.theme/build.properties b/java/com.sap.sse.gwt.theme/build.properties new file mode 100644 index 00000000000..41365161d8b --- /dev/null +++ b/java/com.sap.sse.gwt.theme/build.properties @@ -0,0 +1,9 @@ +source.WEB-INF/classes/ = src/main/java +output.WEB-INF/classes/ = bin +bin.includes = META-INF/,\ + WEB-INF/classes/,\ + WEB-INF/web.xml,\ + com.sap.sse.gwt.theme.SSETheme/,\ + SSETheme.css,\ + SSETheme.html +output.. = WEB-INF/classes/ diff --git a/java/com.sap.sse.gwt.theme/pom.xml b/java/com.sap.sse.gwt.theme/pom.xml new file mode 100644 index 00000000000..eb3460c70c9 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/pom.xml @@ -0,0 +1,105 @@ + + + 4.0.0 + + root + com.sap.sailing + 1.0.0-SNAPSHOT + + com.sap.sse.gwt.theme + eclipse-plugin + + + javax.validation + validation-api + 1.0.0.GA + + + javax.validation + validation-api + 1.0.0.GA + sources + + + com.sap.sailing + com.sap.sse.common + 1.0.0-SNAPSHOT + sources + + + com.sap.sailing + com.sap.sse.gwt + 1.0.0-SNAPSHOT + sources + + + + + + org.eclipse.tycho + tycho-source-plugin + ${tycho-version} + + + plugin-source + generate-sources + + plugin-source + + + + + + org.codehaus.mojo + gwt-maven-plugin + ${gwt.version} + + + + + -Dgwt.persistentunitcache=false + + + com.sap.sse.gwt.theme.SSETheme + + ${basedir}/.generated + 7 + ${basedir} + + + compile + + + + + + + + + + debug.no-gwt-compile + + + + org.codehaus.mojo + gwt-maven-plugin + ${gwt.version} + + + + + + + + help + + + + + + + + + diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/SSETheme.gwt.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/SSETheme.gwt.xml new file mode 100644 index 00000000000..a79745fe37b --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/SSETheme.gwt.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/ThemeEntryPoint.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/ThemeEntryPoint.java new file mode 100644 index 00000000000..99665fc24e3 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/ThemeEntryPoint.java @@ -0,0 +1,20 @@ +package com.sap.sse.gwt.theme.client; + +import com.google.gwt.core.client.EntryPoint; +import com.google.gwt.user.client.ui.RootLayoutPanel; +import com.sap.sse.gwt.theme.client.resources.ThemeResources; +import com.sap.sse.gwt.theme.client.showcase.MainPage; + +/** + * Entry point classes define onModuleLoad(). + */ +public class ThemeEntryPoint implements EntryPoint { + + @Override + public void onModuleLoad() { + ThemeResources.INSTANCE.mediaCss().ensureInjected(); + ThemeResources.INSTANCE.mainCss().ensureInjected(); + + RootLayoutPanel.get().add(new MainPage()); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/imagegallery/ImageGallery.gss b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/imagegallery/ImageGallery.gss new file mode 100644 index 00000000000..e46fdf65acd --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/imagegallery/ImageGallery.gss @@ -0,0 +1,2 @@ +.sampleCssClass { +} \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/imagegallery/ImageGallery.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/imagegallery/ImageGallery.java new file mode 100644 index 00000000000..3eb8d4bd577 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/imagegallery/ImageGallery.java @@ -0,0 +1,19 @@ +package com.sap.sse.gwt.theme.client.component.imagegallery; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; + +public class ImageGallery extends Composite { + private static ImageGalleryUiBinder uiBinder = GWT.create(ImageGalleryUiBinder.class); + + interface ImageGalleryUiBinder extends UiBinder { + } + + public ImageGallery() { + ImageGalleryResources.INSTANCE.css().ensureInjected(); + + initWidget(uiBinder.createAndBindUi(this)); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/imagegallery/ImageGallery.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/imagegallery/ImageGallery.ui.xml new file mode 100644 index 00000000000..a98b61b8104 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/imagegallery/ImageGallery.ui.xml @@ -0,0 +1,9 @@ + + + + + + Image gallery... + + diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/imagegallery/ImageGalleryResources.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/imagegallery/ImageGalleryResources.java new file mode 100644 index 00000000000..b51de3ceda3 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/imagegallery/ImageGalleryResources.java @@ -0,0 +1,16 @@ +package com.sap.sse.gwt.theme.client.component.imagegallery; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +public interface ImageGalleryResources extends ClientBundle { + public static final ImageGalleryResources INSTANCE = GWT.create(ImageGalleryResources.class); + + @Source("ImageGallery.gss") + LocalCss css(); + + public interface LocalCss extends CssResource { + String sampleCssClass(); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.gss b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.gss new file mode 100644 index 00000000000..3251d4bb461 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.gss @@ -0,0 +1,2 @@ +.loadingIndicator { +} \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.java new file mode 100644 index 00000000000..f385113684f --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.java @@ -0,0 +1,19 @@ +package com.sap.sse.gwt.theme.client.component.loadingindicator; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; + +public class LoadingIndicator extends Composite { + private static LoadingIndicatorUiBinder uiBinder = GWT.create(LoadingIndicatorUiBinder.class); + + interface LoadingIndicatorUiBinder extends UiBinder { + } + + public LoadingIndicator() { + LoadingIndicatorResources.INSTANCE.css().ensureInjected(); + + initWidget(uiBinder.createAndBindUi(this)); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.ui.xml new file mode 100644 index 00000000000..2e51dbe3527 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.ui.xml @@ -0,0 +1,9 @@ + + + + + + Loading... + + diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicatorResources.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicatorResources.java new file mode 100644 index 00000000000..4e3672bb46c --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicatorResources.java @@ -0,0 +1,16 @@ +package com.sap.sse.gwt.theme.client.component.loadingindicator; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +public interface LoadingIndicatorResources extends ClientBundle { + public static final LoadingIndicatorResources INSTANCE = GWT.create(LoadingIndicatorResources.class); + + @Source("LoadingIndicator.gss") + LocalCss css(); + + public interface LocalCss extends CssResource { + String loadingIndicator(); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooter.gss b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooter.gss new file mode 100644 index 00000000000..05a39fd654a --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooter.gss @@ -0,0 +1,55 @@ +/** + * Website footer styling + */ +.sitefooter { + font-size: 1rem; + background: #333; + border-top: 5px solid #fff; + color: #fff; + line-height: 5em; + width: 100%; + padding: 0 1.333333333333333em; +} +.sitefooter_copyright { + font-weight: 600; + margin-right: 2em; +} +@media (min-width: 45em) { + .sitefooter_copyright { + float: left; + } +} +.sitefooter_links { + text-align: left; + font-size: 1em; + font-weight: 600; + text-transform: uppercase; +} +.sitefooter_links_link { + display: block; + color: #fff; + text-decoration: none; + margin-right: 2em; +} +.sitefooter_links_link:last-child { + margin-right: 0; +} +@media (min-width: 45em) { + .sitefooter_links_link { + display: inline-block; + } +} +.sitefooter_language { + text-align: left; +} +@media (min-width: 45em) { + .sitefooter_language { + text-align: right; + } +} +.sitefooter_language_link { + color: #f0ab00; + display: inline-block; + cursor: pointer; + margin-left: 0.666666666666667em; +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooter.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooter.java new file mode 100644 index 00000000000..88fc59bed9c --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooter.java @@ -0,0 +1,68 @@ +package com.sap.sse.gwt.theme.client.component.sapfooter; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.http.client.UrlBuilder; +import com.google.gwt.i18n.client.LocaleInfo; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.uibinder.client.UiHandler; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.Window.Location; +import com.google.gwt.user.client.ui.Anchor; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; + +public class SAPFooter extends Composite { + private static SAPFooterPanelUiBinder uiBinder = GWT.create(SAPFooterPanelUiBinder.class); + + interface SAPFooterPanelUiBinder extends UiBinder { + } + + @UiField Anchor changeLanguageLink; + @UiField DivElement languageSelectionDiv; + + private final Map localeAndLanguages; + + public SAPFooter() { + localeAndLanguages = new HashMap(); + localeAndLanguages.put("default", "English"); + localeAndLanguages.put("de", "Deutsch"); + + SAPFooterResources.INSTANCE.css().ensureInjected(); + + initWidget(uiBinder.createAndBindUi(this)); + + updateUI(); + } + + @UiHandler("changeLanguageLink") + public void changeLanguage(ClickEvent e) { + String selectedLanguage = changeLanguageLink.getText(); + String selectedLocale = null; + for (Entry entry: localeAndLanguages.entrySet()) { + if (entry.getValue().equals(selectedLanguage)) { + selectedLocale = entry.getKey(); + } + } + if(selectedLocale != null) { + UrlBuilder builder = Location.createUrlBuilder().setParameter("locale", selectedLocale); + Window.Location.replace(builder.buildString()); + } + } + + private void updateUI() { + LocaleInfo currentLocale = LocaleInfo.getCurrentLocale(); + HashMap languagesToSelectFrom = new HashMap(localeAndLanguages); + languagesToSelectFrom.remove(currentLocale.getLocaleName()); + + if(languagesToSelectFrom.size() == 1) { + changeLanguageLink.setText(languagesToSelectFrom.values().iterator().next()); + } + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooter.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooter.ui.xml new file mode 100644 index 00000000000..e91510e8856 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooter.ui.xml @@ -0,0 +1,33 @@ + + + + + + + + + + \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooterResources.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooterResources.java new file mode 100644 index 00000000000..0009fba40a4 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooterResources.java @@ -0,0 +1,21 @@ +package com.sap.sse.gwt.theme.client.component.sapfooter; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +public interface SAPFooterResources extends ClientBundle { + public static final SAPFooterResources INSTANCE = GWT.create(SAPFooterResources.class); + + @Source("com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooter.gss") + LocalCss css(); + + public interface LocalCss extends CssResource { + String sitefooter(); + String sitefooter_copyright(); + String sitefooter_links(); + String sitefooter_links_link(); + String sitefooter_language(); + String sitefooter_language_link(); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeader.gss b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeader.gss new file mode 100644 index 00000000000..e5753a2d9cd --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeader.gss @@ -0,0 +1,43 @@ +.siteheader { + font-size: 1rem; + background: #333; + border-bottom: 5px solid #f0ab00; + height: 70px; + line-height: 4em; + width: 100%; + overflow: hidden; + text-align:center; +} + +.siteheaderLeft { + float:left; + width: 25%; + text-align: left; +} + +.siteheaderCentered { + width: 50%; + display: inline-block; + margin:0 auto; +} + +.siteheaderRight { + float:right; + width: 25%; + text-align: right; +} + +.logo { + margin: 0.8em 10px; + float: left; +} +.logotitle { + display: inline; + color: #f6f9fc; + font-weight: bold; +} +.pagetitle { + color: #ffffff; + font-size: 1.5rem; + font-weight: bold; +} \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeader.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeader.java new file mode 100644 index 00000000000..83e30aaa36f --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeader.java @@ -0,0 +1,51 @@ +package com.sap.sse.gwt.theme.client.component.sapheader; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.RepeatingCommand; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.ParagraphElement; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.uibinder.client.UiHandler; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sse.gwt.theme.client.util.FullscreenUtil; + +public class SAPHeader extends Composite { + private static SAPHeaderUiBinder uiBinder = GWT.create(SAPHeaderUiBinder.class); + + interface SAPHeaderUiBinder extends UiBinder { + } + + @UiField ParagraphElement applicationNameUi; + @UiField DivElement pageTitleUi; + @UiField Button startFullScreenButton; + + public SAPHeader(String applicationName, String pageTitle, boolean startInAutoScreenMode) { + SAPHeaderResources.INSTANCE.css().ensureInjected(); + + initWidget(uiBinder.createAndBindUi(this)); + + applicationNameUi.setInnerText(applicationName != null ? applicationName : " "); + pageTitleUi.setInnerText(pageTitle); + startFullScreenButton.setVisible(startInAutoScreenMode); + + // the 'fullscreen' button should disappear after some seconds (10) + if(startInAutoScreenMode) { + Scheduler.get().scheduleFixedPeriod(new RepeatingCommand() { + public boolean execute () { + startFullScreenButton.setVisible(false); + return false; + } + }, 10000); + } + } + + @UiHandler("startFullScreenButton") + void startFullScreenClicked(ClickEvent e) { + FullscreenUtil.requestFullscreen(); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeader.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeader.ui.xml new file mode 100644 index 00000000000..1138ddaf708 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeader.ui.xml @@ -0,0 +1,21 @@ + + + + + +
      +
      + +

      +
      +
      +
      +
      +
      + +   +
      +
      +
      +
      diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeaderResources.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeaderResources.java new file mode 100644 index 00000000000..dbc04cc3d51 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeaderResources.java @@ -0,0 +1,26 @@ +package com.sap.sse.gwt.theme.client.component.sapheader; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; +import com.google.gwt.resources.client.ImageResource; + +public interface SAPHeaderResources extends ClientBundle { + public static final SAPHeaderResources INSTANCE = GWT.create(SAPHeaderResources.class); + + @Source("SAPHeader.gss") + LocalCss css(); + + public interface LocalCss extends CssResource { + String siteheader(); + String siteheaderLeft(); + String siteheaderCentered(); + String siteheaderRight(); + String logo(); + String logotitle(); + String pagetitle(); + } + + @Source("logo-small@2x.png") + ImageResource logo(); +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/logo-small@2x.png b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/logo-small@2x.png new file mode 100644 index 00000000000..e5d2daf91f3 Binary files /dev/null and b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/logo-small@2x.png differ diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/videogallery/VideoGallery.gss b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/videogallery/VideoGallery.gss new file mode 100644 index 00000000000..e46fdf65acd --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/videogallery/VideoGallery.gss @@ -0,0 +1,2 @@ +.sampleCssClass { +} \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/videogallery/VideoGallery.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/videogallery/VideoGallery.java new file mode 100644 index 00000000000..c197472e8a3 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/videogallery/VideoGallery.java @@ -0,0 +1,19 @@ +package com.sap.sse.gwt.theme.client.component.videogallery; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; + +public class VideoGallery extends Composite { + private static VideoGalleryUiBinder uiBinder = GWT.create(VideoGalleryUiBinder.class); + + interface VideoGalleryUiBinder extends UiBinder { + } + + public VideoGallery() { + VideoGalleryResources.INSTANCE.css().ensureInjected(); + + initWidget(uiBinder.createAndBindUi(this)); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/videogallery/VideoGallery.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/videogallery/VideoGallery.ui.xml new file mode 100644 index 00000000000..a98b61b8104 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/videogallery/VideoGallery.ui.xml @@ -0,0 +1,9 @@ + + + + + + Image gallery... + + diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/videogallery/VideoGalleryResources.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/videogallery/VideoGalleryResources.java new file mode 100644 index 00000000000..3bc08cc64e2 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/videogallery/VideoGalleryResources.java @@ -0,0 +1,16 @@ +package com.sap.sse.gwt.theme.client.component.videogallery; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +public interface VideoGalleryResources extends ClientBundle { + public static final VideoGalleryResources INSTANCE = GWT.create(VideoGalleryResources.class); + + @Source("VideoGallery.gss") + LocalCss css(); + + public interface LocalCss extends CssResource { + String sampleCssClass(); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/ThemeResources.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/ThemeResources.java new file mode 100644 index 00000000000..d6026a3ab87 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/ThemeResources.java @@ -0,0 +1,140 @@ +package com.sap.sse.gwt.theme.client.resources; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +public interface ThemeResources extends ClientBundle { + public static final ThemeResources INSTANCE = GWT.create(ThemeResources.class); + + @Source("main.gss") + MainCss mainCss(); + + @Source("media.gss") + MediaCss mediaCss(); + + public interface MainCss extends CssResource{ + String wrapper(); + String navbar(); + String navbar_button(); + String navbar_buttonhidden(); + String navbar_buttonactive(); + String button(); + String buttonsmall(); + String buttonstrong(); + String buttoninactive(); + String buttonprimary(); + String buttonred(); + String buttonrefresh(); + String buttonarrowdown(); + String buttonsearch(); + String buttonarrowrightwhite(); + String label(); + String labellarge(); + String morelink(); + String dummy(); + String mainsection(); + String mainsection_header(); + String mainsection_header_title(); + String mainsection_navigation(); + String lightbox(); + String lightbox_overlay(); + String lightbox_content(); + String lightbox_close(); + String stickyheader(); + String slideto(); + String slidetonav(); + String slidetoactive(); + } + + public interface MediaCss extends CssResource{ + String grid(); + String stackenblochen(); + String column(); + String columns(); + String small1(); + String small2(); + String small3(); + String small4(); + String small5(); + String small6(); + String small7(); + String small8(); + String small9(); + String small10(); + String small11(); + String small12(); + String smalloffset0(); + String smalloffset1(); + String smalloffset2(); + String smalloffset3(); + String smalloffset4(); + String smalloffset5(); + String smalloffset6(); + String smalloffset7(); + String smalloffset8(); + String smalloffset9(); + String smalloffset10(); + String smalloffset11(); + String showonsmall(); + String hideonsmall(); + String showonmedium(); + String hideonmedium(); + String showonlarge(); + String hideonlarge(); + String smallcentered(); + String smalluncentered(); + String medium1(); + String medium2(); + String medium3(); + String medium4(); + String medium5(); + String medium6(); + String medium7(); + String medium8(); + String medium9(); + String medium10(); + String medium11(); + String medium12(); + String mediumoffset0(); + String mediumoffset1(); + String mediumoffset2(); + String mediumoffset3(); + String mediumoffset4(); + String mediumoffset5(); + String mediumoffset6(); + String mediumoffset7(); + String mediumoffset8(); + String mediumoffset9(); + String mediumoffset10(); + String mediumoffset11(); + String mediumcentered(); + String mediumuncentered(); + String large1(); + String large2(); + String large3(); + String large4(); + String large5(); + String large6(); + String large7(); + String large8(); + String large9(); + String large10(); + String large11(); + String large12(); + String largeoffset0(); + String largeoffset1(); + String largeoffset2(); + String largeoffset3(); + String largeoffset4(); + String largeoffset5(); + String largeoffset6(); + String largeoffset7(); + String largeoffset8(); + String largeoffset9(); + String largeoffset10(); + String largeoffset11(); + String largecentered(); + String largeuncentered(); + } +} \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages.java new file mode 100644 index 00000000000..32cf93c7b04 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages.java @@ -0,0 +1,17 @@ +package com.sap.sse.gwt.theme.client.resources.i18n; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.i18n.client.Messages; + +public interface TextMessages extends Messages { + public static final TextMessages INSTANCE = GWT.create(TextMessages.class); + + String chooseALanguage(); + + String footerCopyright(); + String footerLanguage(); + String footerLegal(); + String footerPrivacy(); + String footerFeedback(); + +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages.properties b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages.properties new file mode 100644 index 00000000000..b5aa42979c2 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages.properties @@ -0,0 +1,6 @@ +chooseALanguage = Choose a language +footerCopyright = \u00A9 2014 SAP Sailing Analytics +footerFeedback = Feedback +footerLanguage = SAP Sailing offered in +footerLegal = Legal Disclosure +footerPrivacy = Privacy diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages_de.properties b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages_de.properties new file mode 100644 index 00000000000..6b3cbc592e8 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages_de.properties @@ -0,0 +1,7 @@ +chooseALanguage = W\u00E4hle eine Sprache +footerCopyright = \u00A9 2014 SAP Sailing Analytics +footerFeedback = Feedback +footerLanguage = SAP Sailing in +footerLegal = Impressum +footerPrivacy = Datenschutz + diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/main.gss b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/main.gss new file mode 100644 index 00000000000..ea60fb681f6 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/main.gss @@ -0,0 +1,417 @@ +/** + * The main styling styles + */ +*, +*:before, +*:after { + -moz-box-sizing: border-box; + box-sizing: border-box; +} +html, +body { + height: 100%; + font-family: 'Open Sans', sans-serif; + font-size: 93.75%; + line-height: 1.333; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -ms-content-zooming: none; +} +figure { + margin: 0; +} +a { + text-decoration: none; + color: #008fcc; +} +a:hover, +a:focus { + text-decoration: underline; +} +a img { + vertical-align: bottom; +} +hr { + font-size: 1rem; + margin: 0; + height: 1px; + border: none; + border-top: 1px solid #eee; +} +h1 { + font-size: 2.133333333333333em; +} +h2 { + font-size: 1.466666666666667em; + font-weight: bold; + color: #008fcc; + margin-top: 0.7em; + margin-bottom: 0.7em; +} +h3 { + font-size: 1.2em; + font-weight: bold; + margin: 0 0 0.5em; +} +h4 { + font-size: 1em; + font-weight: bold; + color: #008fcc; +} +h5 { + font-size: 0.866666666666667em; + font-weight: 600; + color: #008fcc; +} +figure img { + display: block; +} +figcaption { + margin-top: 1.333333333333333em; +} +.wrapper { + position: relative; + min-height: 100%; + margin-bottom: -5em; + padding-bottom: 8em; +} +.wrapper > * { + padding-left: 1.333333333333333em; + padding-right: 1.333333333333333em; +} +.navbar { + font-size: 1rem; +} +.navbar_button { + font-size: 1.2em; + display: inline-block; + font-weight: 600; + color: #666; + border: 1px solid #dedede; + padding: 0.833333333333333em 0.944444444444444em; + background-color: #eee; + -webkit-transition: background-color 0.2s, border-color 0.2s; + -moz-transition: background-color 0.2s, border-color 0.2s; + transition: background-color 0.2s, border-color 0.2s; + text-decoration: none; + margin-right: 0.833333333333333em; +} +.navbar_buttonhidden { + display: block; +} +.navbar_button:hover, +.navbar_button:focus { + background-color: #dedede; + outline: none; + text-decoration: none; +} +.navbar_buttonactive { + color: #333; + background-color: #fff; + border-bottom: 1px solid #fff; +} +.navbar_buttonactive:hover, +.navbar_buttonactive:focus { + background-color: #dedede; + border-bottom: 1px solid #dedede; +} +.button { + font-size: 1rem; + display: inline-block; + font-weight: 600; + color: #fff; + border: none; + padding: 0.666666666666667em 2em; + border-radius: 0.2em; + background-color: #f0ab00; + -webkit-transition: background-color 0.2s; + -moz-transition: background-color 0.2s; + transition: background-color 0.2s; + text-decoration: none; +} +.button:hover, +.button:focus { + background-color: #d89a00; + outline: none; + text-decoration: none; +} +.buttonsmall { + visibility: inherit; +} +.buttonstrong { + font-size: 0.933333333333333rem; + font-weight: 700; + text-transform: uppercase; + padding: 0.714285714285714em 1.071428571428571em; +} +.buttoninactive { + background-color: #eee; +} +.buttoninactive:hover, +.buttoninactive:focus { + background-color: #eee; +} +.buttonprimary { + background-color: #008fcc; +} +.buttonprimary:hover, +.buttonprimary:focus { + background-color: #0081b8; +} +.buttonred { + background-color: #e94a1b; +} +.buttonred:hover, +.buttonred:focus { + background-color: #d54115; +} +.buttonrefresh { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAppJREFUeNrsl09o03AUx9MEi0rXSuhE66wMKoO5YlXYUc966NF5sB4UOoUdFXYdDnYaCj2VFobCpoeo61xZKQrCGIjt2jlxQ0vr0vWwTdYmzZImTZrnwSbUSbemW4tIHuTy4/d4n9/393t/YgAA5F82gw6oA+qAOqAO+J8DFt+etBrPee6gpp4e7ITLhRgwtEItLcnMyqqYI4iOG2T2QAEAoKmPnraYhbTfD3uZLIlCJhigZzqtzcZpyomJOvsqXG59N49USCalfDwmSxxbu15hSZJ5d+VyWwCLYbtd5rc2Vah8PMYuuN0UgaHKHorAUHbB7a4wqZQqprC9XZxzOFoOSBEYWiYnJwEAhJTPVwv21zN4feyokPL51MNQy5+pV0Zjy6+YIjCUW/R6G91fXnv+TIHkEg/utwywtPzooRYwVckQjstlqgAAIP6cn28JYOnL8HCzKgAAImSCASWztWS1ZjgVMjk0pAWQi9+7q/gy7/v7G/VD96uTTPRCL3q8u1vaiESUNWkjEsHMTicTOe/Yt5DPnj5VSgx6MYvrkrJmtHs8pcSglwl32Q6tUO98uHZVVe+3Gg378qtjY7tvgP/+9MmhvkE6hOMgSyIAQDUrNb1BfmX0sVY4zVlczUCQRYahQziuGfLryIgWOM2A7CfPbUWFcvbli4b9Pg7cbEsvpggMlQrJpAIpZIIB+o3JtNf+qmLAfxsfb08vnnM4antxZSeTrteLpXw8pvZifmuzOHvG1r5phiXJPycrjpXy8VitwuohuNw6E3X2tW3cAgCEnum0Cmm/X8nseib8mJigpy3mZuMceKJmwl22I2cHbmEW10XU3NuLIAhSKSwm5FKWFLNTUx3XM2v6P4kOqAPqgDqgDqgD1rVfAwAVWx4m+SNozQAAAABJRU5ErkJggg=="); +} +.buttonrefresh, +.buttonarrowdown { + background-repeat: no-repeat; + background-position: left top; + background-size: cover; + width: 2.333333333333333em; + height: 0; + padding: 2.333333333333333em 0 0 0; + border: 0; + overflow: hidden; +} +.buttonsearch { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2NTg0MEE1MTVFN0UxMUU0QkNGMTk0Q0UzOUEwNUNFMyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo2NTg0MEE1MjVFN0UxMUU0QkNGMTk0Q0UzOUEwNUNFMyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjY1ODQwQTRGNUU3RTExRTRCQ0YxOTRDRTM5QTA1Q0UzIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjY1ODQwQTUwNUU3RTExRTRCQ0YxOTRDRTM5QTA1Q0UzIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+tERf2gAAArFJREFUeNrMmE1kXFEUx28SwjAMjxCGIQwhzCoMQysVugqlhJBVyCqLbELpqtmEUFJddZFNFhVSpd20uqgw1VIhixgSIqREYiKRSExkhNTruZzhOO5999737m3m8DdzZt6773fP/Trn9cRxLBysD/QUNAaKQEP4eQI6xc/voN/Cl0lAC1VBG6Cr2M6aoHegkmX7WpkuKIPW4/R2B1oBRSEAJ0G3BoAz0DaoZbjuGEfBG+CS5kG7oFegiua+MYxYU3Gv7OyUD0AV3CFo2qHhHGhBM2efZQGcUjT4ER+YZg7JRbLF2msljEAiYFkx515nXYXYuS+s3QNQvyvghiJywpPyoAZr/4ULYFXRw5xHQKkh3HY6dmGz/fTifv2S7d+LoLbwa39Ab4gvT6BZm5MkYj3b9Rw5qojN84ZNBB+B+gnzexHOLvGs7lgFVEy6oQNI7asIax+YP2oCLBP/BrQXGHCH+RUT4ADxZcr0NzDgEfMLJsA88dsivN0wP2cCvCZ+/j8ARsw/NwGeEr+IWXNIK7kC7hNfbje1wIA1xQaeCPiL/TYRGHCSzfkfppMkx/K2Q9tMI2X6dU+e9dPmJGmzzVNWanOBorfE5viqbVWnyjSKnqNXY9E7thkp6qywlGvLY8pVVNQpc64Ja4S9ovbJA2QJMyRqm2lrkqoi7W9kKMBrmgpvPEtVN60pGZdBBYeorRnePAxnqYufa4rxFg677MQoSdlL6MtS8xtbDJkgk/6s4J7ow+qgCcVwGyFtC/CLlGBNtlpHXCFtJ3sBQesWUPe4Smc0+5wTZI/j+0GBCa488Acx+xkk7waP8Gw15ZUjoE28lybLT1jyIkTACs4kq0g+JKAV5EMDGiHTzMEQNgyqK+bk424B1EF+7hXdY/u4immNJLopgh2TLxLe4vf5fwIMADtbDGrkqxb2AAAAAElFTkSuQmCC"); + background-color: transparent; + background-size: contain; + background-position: center center; + background-repeat: no-repeat; + width: 1.333333333333333em; + height: 1.333333333333333em; + padding: 0; +} +.buttonsearch:hover, +.buttonsearch:focus { + background-color: transparent; + outline: none; + text-decoration: none; +} +.buttonarrowdown { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAALFJREFUeNrs1TEOgjAUxnFx5iDscgPvwXkYuYdXwBPgAYwrsxPGIAPPvKQkxLQO5UE0+X9J06Ff0186tImI7H45CUCAAAECBAgQIMA1s1+y+dkUpUXna/QGY0Z/q0QztCcJddyauG7UOdHA8XWXKT7khNO47rbArj5kIeQnTrubA0NIS9xioA9piTMB+pBWODPgHGmJ02H6UD/OeaZzerxc+eoAAgQIECBAgAD/AvgWYACm/8hEADYaXwAAAABJRU5ErkJggg=="); + width: 2.666666666666667em; + padding: 2.733333333333333em 0 0 0; +} +.buttonarrowrightwhite:after { + content: ''; + display: inline-block; + margin-left: 0.666666666666667em; + width: 0.466666666666667em; + height: 0.8em; + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAMCAYAAACulacQAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAFtJREFUeNpi+P///wEgPgzEokDMgIxBxMn/EHAVXQGIkIBKYCiAqUJXIIEsiVUBA5ojRJEUnGFiwAR/ofQvXLpQjMXpILxeOY8rEEAOegfER4DYAYhfI7sMIMAAiXvwy2upI1UAAAAASUVORK5CYII="); + background-size: contain; + background-repeat: no-repeat; +} +.label { + display: inline-block; + font-size: 0.8rem; + font-weight: 600; + color: #fff; + padding: 0.066666666666667em 0.666666666666667em; + position: relative; + margin-left: 0.833333333333333em; + height: 1.666666666666667em; + line-height: 1.666666666666667em; +} +.label:before { + content: ''; + position: absolute; + top: 0; + left: -0.833333333333333em; + width: 0; + height: 0; + border-top: 0.833333333333333em solid transparent; + border-bottom: 0.833333333333333em solid transparent; + border-right: 0.833333333333333em solid transparent; +} +.labellarge { + height: 2em; + line-height: 1.75em; + margin-left: 1em; +} +.labellarge:before { + left: -1em; + border-top-width: 1em; + border-bottom-width: 1em; + border-right-width: 1em; +} +.label[datalabeltype='live'] { + background-color: #e94a1b; + vertical-align: middle; + margin-top: -0.416666666666667em; +} +.label[datalabeltype='live']:before { + border-right-color: #e94a1b; +} +.label[datalabeltype='finished'] { + background-color: #8ab54e; + vertical-align: middle; + margin-top: -0.416666666666667em; +} +.label[datalabeltype='finished']:before { + border-right-color: #8ab54e; +} +.label[datalabeltype='progress'] { + background-color: #f0ab00; + vertical-align: middle; + margin-top: -0.416666666666667em; +} +.label[datalabeltype='progress']:before { + border-right-color: #f0ab00; +} +.label[datalabeltype='upcoming'] { + color: #333; + background-color: #eee; + vertical-align: middle; + margin-top: -0.416666666666667em; +} +.label[datalabeltype='upcoming']:before { + border-right-color: #eee; +} +.label[datalabeltype='new'], +.label[datalabeltype='updated'] { + background-color: #f0ab00; + text-transform: uppercase; + vertical-align: middle; +} +.label[datalabeltype='new']:before, +.label[datalabeltype='updated']:before { + border-right-color: #f0ab00; +} +.label[datalabeltype='ess'] { + background-color: #a71724; +} +.label[datalabeltype='ess']:before { + border-right-color: #a71724; +} +.label[datalabeltype='worldcup'] { + background-color: #c79242; +} +.label[datalabeltype='worldcup']:before { + border-right-color: #c79242; +} +.label[datalabeltype='bundesliga'] { + background-color: #005fa7; +} +.label[datalabeltype='bundesliga']:before { + border-right-color: #005fa7; +} +.morelink { + color: #008fcc; + text-transform: uppercase; + text-align: center; + font-size: 0.933333333333333em; + font-weight: 700; + padding: 1.866666666666667em; + display: block; + text-decoration: none; +} +.morelink:hover, +.morelink:focus { + text-decoration: underline; +} +.dummy { + font-size: 1rem; + min-height: 25em; + position: relative; +} +.dummy:after { + content: 'Dummy content'; + position: absolute; + top: 50%; + left: 50%; + font-weight: 700; + font-size: 6em; + color: #f7f7f7; + text-shadow: 1px 1px 0 #eee, 2px 2px 0 #eee, 3px 3px 0 #eee, 4px 4px 0 #eee, 5px 5px 0 #eee, 6px 6px 0 #eee, 7px 7px 0 #eee; + margin: -100px 0 0 -350px; +} +.mainsection { + padding-top: 5.333333333333333em; + padding-bottom: 5.333333333333333em; +} +.mainsection_header { + text-align: center; + margin-bottom: 3.666666666666667em; +} +.mainsection_header p { + margin-top: 0; + font-size: 1.2em; +} +.mainsection_header_title { + margin: 0; + font-size: 2.4em; + text-align: center; +} +.mainsection_navigation { + text-align: center; +} +.lightbox { + font-size: 1rem; +} +.lightbox_overlay { + display: none; + font-size: 1rem; + background: rgba(0,0,0,0.75); + width: 100%; + height: 100%; + position: fixed; + left: 0; + top: 0; + z-index: 10; + -webkit-transition: opacity 0.3s ease; + -moz-transition: opacity 0.3s ease; + transition: opacity 0.3s ease; +} +.lightbox_content { + position: absolute; + top: 50%; + left: 50%; + -webkit-transform: translate(-50%, -50%); + -moz-transform: translate(-50%, -50%); + -ms-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + max-width: 90%; + max-height: 90%; + overflow: hidden; +} +.lightbox_close { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAHVJREFUeNqsk4EJACEIReWawY2bMLh5LME7JNQEE4So/x9pCUTUVvaVuBKSieJpIAuOkYSgaDn6t/EmIdrMHtQHJ4g2/5pd4EFM8w7whK7ZAliQEZWWaVbY3AeqcbuEUhMj4fEZ8XTV6CNlzC7kyjCVxnkKMABEXRUAnXsRRQAAAABJRU5ErkJggg=="); + background-repeat: no-repeat; + background-position: center center; + width: 1.333333333333333em; + height: 0; + padding-top: 1.333333333333333em; + position: absolute; + top: 1.333333333333333em; + right: 1.333333333333333em; + cursor: pointer; + z-index: 2; +} +.stickyheader { + background: inherit; +} +.slideto { + background: inherit; + border-bottom: 0.266666666666667em solid transparent; +} +.slidetonav { + background: inherit; +} +.slidetoactive { + border-bottom-color: #f0ab00; + text-decoration: none; + color: #dedede; +} +.slidetoactive:hover, +.slidetoactive:focus { + border-bottom-color: #d89a00; +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/media.gss b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/media.gss new file mode 100644 index 00000000000..611ff15f214 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/media.gss @@ -0,0 +1,450 @@ +/** + * The grid styles + */ +.grid, +.stackenblochen { + display: block; + width: 100%; + max-width: 80rem; + list-style: none; + margin: 0 auto; + zoom: 1; +} +.grid:before, +.stackenblochen:before, +.grid:after, +.stackenblochen:after { + content: ""; + display: table; +} +.grid:after, +.stackenblochen:after { + clear: both; +} +.grid *, +.stackenblochen * { + -moz-box-sizing: border-box; + box-sizing: border-box; +} +.grid .grid, +.stackenblochen .grid, +.grid .stackenblochen, +.stackenblochen .stackenblochen { + width: auto; + max-width: none; + margin-left: -1rem; + margin-right: -1rem; +} +.column, +.columns { + float: left; + padding: 0 1rem; + position: relative; + width: 100%; +} +.column img, +.columns img { + max-width: 100%; + height: auto; + ms-interpolation-mode: bicubic; +} +.small1 { + width: 8.3333333333333%; +} +.small2 { + width: 16.6666666666667%; +} +.small3 { + width: 25%; +} +.small4 { + width: 33.3333333333333%; +} +.small5 { + width: 41.6666666666667%; +} +.small6 { + width: 50%; +} +.small7 { + width: 58.3333333333333%; +} +.small8 { + width: 66.6666666666667%; +} +.small9 { + width: 75%; +} +.small10 { + width: 83.3333333333333%; +} +.small11 { + width: 91.6666666666667%; +} +.small12 { + width: 100%; +} +.smalloffset0 { + margin-left: 0% !important; +} +.smalloffset1 { + margin-left: 8.3333333333333% !important; +} +.smalloffset2 { + margin-left: 16.6666666666667% !important; +} +.smalloffset3 { + margin-left: 25% !important; +} +.smalloffset4 { + margin-left: 33.3333333333333% !important; +} +.smalloffset5 { + margin-left: 41.6666666666667% !important; +} +.smalloffset6 { + margin-left: 50% !important; +} +.smalloffset7 { + margin-left: 58.3333333333333% !important; +} +.smalloffset8 { + margin-left: 66.6666666666667% !important; +} +.smalloffset9 { + margin-left: 75% !important; +} +.smalloffset10 { + margin-left: 83.3333333333333% !important; +} +.smalloffset11 { + margin-left: 91.6666666666667% !important; +} +.showonsmall { + display: inherit !important; +} +.hideonsmall { + display: none !important; +} +table.showonsmall { + display: table !important; +} +tr.showonsmall { + display: table-row !important; +} +td.showonsmall, +th.showonsmall { + display: table-cell !important; +} +.showonmedium { + display: none !important; +} +.hideonmedium { + display: inherit !important; +} +table.hideonmedium { + display: table !important; +} +tr.hideonmedium { + display: table-row !important; +} +td.hideonmedium, +th.hideonmedium { + display: table-cell !important; +} +.showonlarge { + display: none !important; +} +.hideonlarge { + display: inherit !important; +} +table.hideonlarge { + display: table !important; +} +tr.hideonlarge { + display: table-row !important; +} +td.hideonlarge, +th.hideonlarge { + display: table-cell !important; +} +.column.smallcentered, +.columns.smallcentered { + margin-left: auto; + margin-right: auto; + float: none; +} +.column.smalluncentered, +.columns.smalluncentered { + margin-left: 0; + margin-right: 0; + float: left !important; +} +@media (min-width: 45em) { + .medium1 { + width: 8.3333333333333%; + } + .medium2 { + width: 16.6666666666667%; + } + .medium3 { + width: 25%; + } + .medium4 { + width: 33.3333333333333%; + } + .medium5 { + width: 41.6666666666667%; + } + .medium6 { + width: 50%; + } + .medium7 { + width: 58.3333333333333%; + } + .medium8 { + width: 66.6666666666667%; + } + .medium9 { + width: 75%; + } + .medium10 { + width: 83.3333333333333%; + } + .medium11 { + width: 91.6666666666667%; + } + .medium12 { + width: 100%; + } + .mediumoffset0 { + margin-left: 0% !important; + } + .mediumoffset1 { + margin-left: 8.3333333333333% !important; + } + .mediumoffset2 { + margin-left: 16.6666666666667% !important; + } + .mediumoffset3 { + margin-left: 25% !important; + } + .mediumoffset4 { + margin-left: 33.3333333333333% !important; + } + .mediumoffset5 { + margin-left: 41.6666666666667% !important; + } + .mediumoffset6 { + margin-left: 50% !important; + } + .mediumoffset7 { + margin-left: 58.3333333333333% !important; + } + .mediumoffset8 { + margin-left: 66.6666666666667% !important; + } + .mediumoffset9 { + margin-left: 75% !important; + } + .mediumoffset10 { + margin-left: 83.3333333333333% !important; + } + .mediumoffset11 { + margin-left: 91.6666666666667% !important; + } + .showonsmall { + display: none !important; + } + .hideonsmall { + display: inherit !important; + } + table.hideonsmall { + display: table !important; + } + tr.hideonsmall { + display: table-row !important; + } + td.hideonsmall, + th.hideonsmall { + display: table-cell !important; + } + .showonmedium { + display: inherit !important; + } + .hideonmedium { + display: none !important; + } + table.showonmedium { + display: table !important; + } + tr.showonmedium { + display: table-row !important; + } + td.showonmedium, + th.showonmedium { + display: table-cell !important; + } + .showonlarge { + display: none !important; + } + .hideonlarge { + display: inherit !important; + } + table.hideonlarge { + display: table !important; + } + tr.hideonlarge { + display: table-row !important; + } + td.hideonlarge, + th.hideonlarge { + display: table-cell !important; + } + .column.mediumcentered, + .columns.mediumcentered { + margin-left: auto; + margin-right: auto; + float: none; + } + .column.mediumuncentered, + .columns.mediumuncentered { + margin-left: 0; + margin-right: 0; + float: left !important; + } +} +@media (min-width: 61em) { + .large1 { + width: 8.3333333333333%; + } + .large2 { + width: 16.6666666666667%; + } + .large3 { + width: 25%; + } + .large4 { + width: 33.3333333333333%; + } + .large5 { + width: 41.6666666666667%; + } + .large6 { + width: 50%; + } + .large7 { + width: 58.3333333333333%; + } + .large8 { + width: 66.6666666666667%; + } + .large9 { + width: 75%; + } + .large10 { + width: 83.3333333333333%; + } + .large11 { + width: 91.6666666666667%; + } + .large12 { + width: 100%; + } + .largeoffset0 { + margin-left: 0% !important; + } + .largeoffset1 { + margin-left: 8.3333333333333% !important; + } + .largeoffset2 { + margin-left: 16.6666666666667% !important; + } + .largeoffset3 { + margin-left: 25% !important; + } + .largeoffset4 { + margin-left: 33.3333333333333% !important; + } + .largeoffset5 { + margin-left: 41.6666666666667% !important; + } + .largeoffset6 { + margin-left: 50% !important; + } + .largeoffset7 { + margin-left: 58.3333333333333% !important; + } + .largeoffset8 { + margin-left: 66.6666666666667% !important; + } + .largeoffset9 { + margin-left: 75% !important; + } + .largeoffset10 { + margin-left: 83.3333333333333% !important; + } + .largeoffset11 { + margin-left: 91.6666666666667% !important; + } + .showonsmall { + display: none !important; + } + .hideonsmall { + display: inherit !important; + } + table.hideonsmall { + display: table !important; + } + tr.hideonsmall { + display: table-row !important; + } + td.hideonsmall, + th.hideonsmall { + display: table-cell !important; + } + .showonmedium { + display: none !important; + } + .hideonmedium { + display: inherit !important; + } + table.hideonmedium { + display: table !important; + } + tr.hideonmedium { + display: table-row !important; + } + td.hideonmedium, + th.hideonmedium { + display: table-cell !important; + } + .showonlarge { + display: inherit !important; + } + .hideonlarge { + display: none !important; + } + table.showonlarge { + display: table !important; + } + tr.showonlarge { + display: table-row !important; + } + td.showonlarge, + th.showonlarge { + display: table-cell !important; + } + .column.largecentered, + .columns.largecentered { + margin-left: auto; + margin-right: auto; + float: none; + } + .column.largeuncentered, + .columns.largeuncentered { + margin-left: 0; + margin-right: 0; + float: left !important; + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ComponentShowCase.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ComponentShowCase.java new file mode 100644 index 00000000000..bd6f890cc59 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ComponentShowCase.java @@ -0,0 +1,53 @@ +package com.sap.sse.gwt.theme.client.showcase; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiChild; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; + +public class ComponentShowCase extends Composite { + + private static ComponentShowCaseUiBinder uiBinder = GWT.create(ComponentShowCaseUiBinder.class); + + interface ComponentShowCaseUiBinder extends UiBinder { + } + + @UiField + DivElement componentNameUi; + + @UiField + SimplePanel componentDescriptionUi; + + @UiField + SimplePanel componentPanelUi; + + private String name; + + public ComponentShowCase() { + initWidget(uiBinder.createAndBindUi(this)); + } + + public void setName(String name) { + this.name = name; + componentNameUi.setInnerText(name); + } + + @UiChild + public void addDescription(Widget description) { + componentDescriptionUi.setWidget(description); + } + + @UiChild + public void addComponent(Widget w) { + componentPanelUi.setWidget(w); + } + + public String getName() { + return this.name; + } + +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ComponentShowCase.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ComponentShowCase.ui.xml new file mode 100644 index 00000000000..eeabbc8f25d --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ComponentShowCase.ui.xml @@ -0,0 +1,29 @@ + + + + .outerpanel { + margin: 20px; + } + .componentName { + font-size: 20px; + font-weight: bold; + magin-bottom: 10px; + + } + .componentDescription { + margin-bottom: 30px; + border-bottom: 3px solid black; + padding: 20px; + width: 100%; + + } + + +
      + + + + +
      +
      \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.java new file mode 100644 index 00000000000..7ab3fd01d6c --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.java @@ -0,0 +1,30 @@ +package com.sap.sse.gwt.theme.client.showcase; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; + +public class MainPage extends Composite { + + private static MainPageUiBinder uiBinder = GWT.create(MainPageUiBinder.class); + + interface MainPageUiBinder extends UiBinder { + } + + @UiField(provided = true) + SimplePanel contentUi; + + @UiField(provided = true) + ShowcaseStackLayoutPanel stackLayoutPanelUi; + + public MainPage() { + contentUi = new SimplePanel(); + stackLayoutPanelUi = new ShowcaseStackLayoutPanel(Unit.EM); + stackLayoutPanelUi.setTarget(contentUi); + initWidget(uiBinder.createAndBindUi(this)); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.ui.xml new file mode 100644 index 00000000000..3dedb3af0cd --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.ui.xml @@ -0,0 +1,42 @@ + + + + + + + Components + + + + + + + + + + + + + + + + + + + + Styleguide + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/Section.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/Section.java new file mode 100644 index 00000000000..d6407623138 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/Section.java @@ -0,0 +1,50 @@ +package com.sap.sse.gwt.theme.client.showcase; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiChild; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.AcceptsOneWidget; +import com.google.gwt.user.client.ui.Anchor; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; + +public class Section extends Composite { + private static ShowCaseSectionUiBinder uiBinder = GWT.create(ShowCaseSectionUiBinder.class); + + interface ShowCaseSectionUiBinder extends UiBinder { + } + + private AcceptsOneWidget target; + + @UiField + FlowPanel panelUi; + + public Section() { + initWidget(uiBinder.createAndBindUi(this)); + } + + public void setTargetContentArea(AcceptsOneWidget target) { + this.target = target; + } + + @UiChild + public void addItem(final Widget widget, String name) { + SimplePanel p = new SimplePanel(); + Anchor showCaseItem = new Anchor(name); + p.setWidget(showCaseItem); + showCaseItem.addClickHandler(new ClickHandler() { + + @Override + public void onClick(ClickEvent event) { + target.setWidget(widget); + } + }); + + panelUi.add(p); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/Section.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/Section.ui.xml new file mode 100644 index 00000000000..7f8562acc98 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/Section.ui.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ShowCaseResources.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ShowCaseResources.java new file mode 100644 index 00000000000..2147603cd48 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ShowCaseResources.java @@ -0,0 +1,31 @@ +package com.sap.sse.gwt.theme.client.showcase; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +public interface ShowCaseResources extends ClientBundle { + public static final ShowCaseResources INSTANCE = GWT.create(ShowCaseResources.class); + + @Source("showcase.gss") + ShowcaseCss css(); + + public interface ShowcaseCss extends CssResource{ + String showcaseHeader(); + String showcaseContent(); + String styleGuideHeader(); + String styleGuideContent(); + + String styleGuideRow(); + String styleGuideRowDescription(); + String styleGuideRowDescriptionTitle(); + String styleGuideRowContent(); + String styleGuideRowCode(); + String styleGuideRowDescription_codeLink(); + + // TODO: these style should come from the main.css + String styleGuideDetailsTypeface_semibold(); + String styleGuideDetailsTypeface_bold(); + String styleGuideDetailsTypeface_regular(); + } +} \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ShowcaseStackLayoutPanel.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ShowcaseStackLayoutPanel.java new file mode 100644 index 00000000000..4889e6aa5ec --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ShowcaseStackLayoutPanel.java @@ -0,0 +1,27 @@ +package com.sap.sse.gwt.theme.client.showcase; + +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.StackLayoutPanel; +import com.google.gwt.user.client.ui.Widget; + +public class ShowcaseStackLayoutPanel extends StackLayoutPanel { + private SimplePanel target; + + public ShowcaseStackLayoutPanel(Unit unit) { + super(unit); + } + + public void setTarget(SimplePanel target) { + this.target = target; + } + + @Override + public void add(Widget widget, String header, boolean asHtml, double headerSize) { + if (widget instanceof Section) { + Section section = (Section) widget; + section.setTargetContentArea(target); + } + super.add(widget, header, asHtml, headerSize); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/ImageGalleryShowcase.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/ImageGalleryShowcase.java new file mode 100644 index 00000000000..aee0647ff08 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/ImageGalleryShowcase.java @@ -0,0 +1,23 @@ +package com.sap.sse.gwt.theme.client.showcase.components; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sse.gwt.theme.client.component.imagegallery.ImageGallery; + +public class ImageGalleryShowcase extends Composite { + + private static ImageGalleryShowcaseUiBinder uiBinder = GWT.create(ImageGalleryShowcaseUiBinder.class); + + interface ImageGalleryShowcaseUiBinder extends UiBinder { + } + + @UiField + ImageGallery imageGallery; + + public ImageGalleryShowcase() { + initWidget(uiBinder.createAndBindUi(this)); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/ImageGalleryShowcase.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/ImageGalleryShowcase.ui.xml new file mode 100644 index 00000000000..018e825f9e1 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/ImageGalleryShowcase.ui.xml @@ -0,0 +1,20 @@ + + + + + + + An image gallery. +

      Import: xmlns:sap="urn:import:com.sap.sse.gwt.theme.client.component.imagegallery"

      +
      +
      + + + + + +
      +
      \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/LoadingIndicatorShowcase.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/LoadingIndicatorShowcase.java new file mode 100644 index 00000000000..dcd61d485e6 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/LoadingIndicatorShowcase.java @@ -0,0 +1,23 @@ +package com.sap.sse.gwt.theme.client.showcase.components; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sse.gwt.theme.client.component.loadingindicator.LoadingIndicator; + +public class LoadingIndicatorShowcase extends Composite { + + private static LoadingIndicatorShowcaseUiBinder uiBinder = GWT.create(LoadingIndicatorShowcaseUiBinder.class); + + interface LoadingIndicatorShowcaseUiBinder extends UiBinder { + } + + @UiField + LoadingIndicator loadingIndicator; + + public LoadingIndicatorShowcase() { + initWidget(uiBinder.createAndBindUi(this)); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/LoadingIndicatorShowcase.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/LoadingIndicatorShowcase.ui.xml new file mode 100644 index 00000000000..ee194856838 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/LoadingIndicatorShowcase.ui.xml @@ -0,0 +1,20 @@ + + + + + + + A Loading indicator. +

      Import: xmlns:sap="urn:import:com.sap.sse.gwt.theme.client.component.loadingindicator"

      +
      +
      + + + + + +
      +
      \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/SAPFooterShowcase.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/SAPFooterShowcase.java new file mode 100644 index 00000000000..7b570db82ce --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/SAPFooterShowcase.java @@ -0,0 +1,25 @@ +package com.sap.sse.gwt.theme.client.showcase.components; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sse.gwt.theme.client.component.sapfooter.SAPFooter; + +public class SAPFooterShowcase extends Composite { + + private static SAPHeaderShowcaseUiBinder uiBinder = GWT.create(SAPHeaderShowcaseUiBinder.class); + + interface SAPHeaderShowcaseUiBinder extends UiBinder { + } + + @UiField(provided = true) + SAPFooter sapFooter; + + public SAPFooterShowcase() { + sapFooter = new SAPFooter(); + + initWidget(uiBinder.createAndBindUi(this)); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/SAPFooterShowcase.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/SAPFooterShowcase.ui.xml new file mode 100644 index 00000000000..2b993681cc1 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/SAPFooterShowcase.ui.xml @@ -0,0 +1,20 @@ + + + + + + + A SAP footer. +

      Import: xmlns:sap="urn:import:com.sap.sse.gwt.theme.client.component.sapfooter"

      +
      +
      + + + + + +
      +
      \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/SAPHeaderShowcase.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/SAPHeaderShowcase.java new file mode 100644 index 00000000000..aa07434270e --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/SAPHeaderShowcase.java @@ -0,0 +1,25 @@ +package com.sap.sse.gwt.theme.client.showcase.components; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sse.gwt.theme.client.component.sapheader.SAPHeader; + +public class SAPHeaderShowcase extends Composite { + + private static SAPHeaderShowcaseUiBinder uiBinder = GWT.create(SAPHeaderShowcaseUiBinder.class); + + interface SAPHeaderShowcaseUiBinder extends UiBinder { + } + + @UiField(provided = true) + SAPHeader sapHeader; + + public SAPHeaderShowcase() { + sapHeader = new SAPHeader("Sailing Analytics", "Demo page", false); + + initWidget(uiBinder.createAndBindUi(this)); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/SAPHeaderShowcase.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/SAPHeaderShowcase.ui.xml new file mode 100644 index 00000000000..ccf44dc116c --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/SAPHeaderShowcase.ui.xml @@ -0,0 +1,20 @@ + + + + + + + A simple header with SAP logo and title. +

      Import: xmlns:sap="urn:import:com.sap.sse.gwt.theme.client.component.sapheader"

      +
      +
      + + + + + +
      +
      \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/VideoGalleryShowcase.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/VideoGalleryShowcase.java new file mode 100644 index 00000000000..41d1ebc4a22 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/VideoGalleryShowcase.java @@ -0,0 +1,23 @@ +package com.sap.sse.gwt.theme.client.showcase.components; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sse.gwt.theme.client.component.videogallery.VideoGallery; + +public class VideoGalleryShowcase extends Composite { + + private static VideoGalleryShowcaseUiBinder uiBinder = GWT.create(VideoGalleryShowcaseUiBinder.class); + + interface VideoGalleryShowcaseUiBinder extends UiBinder { + } + + @UiField + VideoGallery videoGallery; + + public VideoGalleryShowcase() { + initWidget(uiBinder.createAndBindUi(this)); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/VideoGalleryShowcase.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/VideoGalleryShowcase.ui.xml new file mode 100644 index 00000000000..cf7212aaced --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/VideoGalleryShowcase.ui.xml @@ -0,0 +1,20 @@ + + + + + + + A video gallery. +

      Import: xmlns:sap="urn:import:com.sap.sse.gwt.theme.client.component.videogallery"

      +
      +
      + + + + + +
      +
      \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/showcase.gss b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/showcase.gss new file mode 100644 index 00000000000..1a9a5c4b88e --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/showcase.gss @@ -0,0 +1,78 @@ +.showcaseHeader { + width: 100%; + display: block; + margin: 5px; +} + +.showcaseContent { + width: 100%; + display: block; + margin: 5px; +} + +.styleGuideHeader { + width: 100%; + display: block; + margin: 5px; +} + +.styleGuideContent { + width: 100%; + display: block; + margin: 5px; +} + +.styleGuideRow { + clear: both; + margin-top: 50px; + overflow: hidden; +} + +.styleGuideRowDescription { + float: left; + font-weight: 700; +} + +.styleGuideRowDescriptionTitle { + float: left; +} + +.styleGuideRowContent { + float: left; +} + +.styleGuideRowCode { + background-color: #EEEEEE; + border: 1px solid #CCCCCC; + display: none; + float: left; + font-family: "Lucida Console",Monaco,"Courier New",Courier,monospace; + margin-top: 20px; + padding: 20px 10px; +} + +.styleGuideRowDescription_codeLink { + color: #999999; + display: none; + float: right; + font-family: monospace; + font-size: 12px; + font-weight: 400; + line-height: 23px; + margin-right: 25px; +} + +.styleGuideDetailsTypeface_semibold { + font-size: 22px; + font-weight: 600; +} + +.styleGuideDetailsTypeface_regular { + font-size: 22px; +} + +.styleGuideDetailsTypeface_bold { + font-size: 22px; + font-weight: 700; +} + diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/styleguide/AbstractStyleguideComposite.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/styleguide/AbstractStyleguideComposite.java new file mode 100644 index 00000000000..45919a4a389 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/styleguide/AbstractStyleguideComposite.java @@ -0,0 +1,13 @@ +package com.sap.sse.gwt.theme.client.showcase.styleguide; + +import com.google.gwt.user.client.ui.Composite; +import com.sap.sse.gwt.theme.client.resources.ThemeResources; +import com.sap.sse.gwt.theme.client.showcase.ShowCaseResources; + +public class AbstractStyleguideComposite extends Composite { + public AbstractStyleguideComposite() { + ThemeResources.INSTANCE.mediaCss().ensureInjected(); + ThemeResources.INSTANCE.mainCss().ensureInjected(); + ShowCaseResources.INSTANCE.css().ensureInjected(); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/styleguide/TypefaceStyleguide.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/styleguide/TypefaceStyleguide.java new file mode 100644 index 00000000000..f6162d6643f --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/styleguide/TypefaceStyleguide.java @@ -0,0 +1,19 @@ +package com.sap.sse.gwt.theme.client.showcase.styleguide; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.user.client.ui.Widget; + +public class TypefaceStyleguide extends AbstractStyleguideComposite { + + private static TypefaceStyleguideUiBinder uiBinder = GWT.create(TypefaceStyleguideUiBinder.class); + + interface TypefaceStyleguideUiBinder extends UiBinder { + } + + public TypefaceStyleguide() { + super(); + + initWidget(uiBinder.createAndBindUi(this)); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/styleguide/TypefaceStyleguide.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/styleguide/TypefaceStyleguide.ui.xml new file mode 100644 index 00000000000..64479c7583c --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/styleguide/TypefaceStyleguide.ui.xml @@ -0,0 +1,44 @@ + + + + + + + + Typeface styleguide + + + + +
      +
      +
      +
      REGULAR
      + </> +
      +
      Grumpy wizards make toxic brew for the evil queen and jack.
      +
      Code und sooo
      +
      +
      +
      +
      SEMIBOLD
      + </> +
      +
      Grumpy wizards make toxic brew for the evil queen and jack.
      +
      Code und sooo
      +
      +
      +
      +
      BOLD
      + </> +
      +
      Grumpy wizards make toxic brew for the evil queen and jack.
      +
      Code und sooo
      +
      +
      +
      +
      +
      +
      \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/util/FullscreenUtil.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/util/FullscreenUtil.java new file mode 100644 index 00000000000..27ff25bebd3 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/util/FullscreenUtil.java @@ -0,0 +1,27 @@ +package com.sap.sse.gwt.theme.client.util; + +/** + * A utility class to set the browser to fullscreen mode + * @author c5163874 (Frank) + * + */ +public class FullscreenUtil { + + public static void requestFullscreen() { + nativeRequestFullScreen(); + }; + + private static native void nativeRequestFullScreen() /*-{ + var element = $doc.documentElement; + if(element.requestFullscreen) { + element.requestFullscreen(); + } else if(element.mozRequestFullScreen) { + element.mozRequestFullScreen(); + } else if(element.webkitRequestFullscreen) { + element.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); + } else if(element.msRequestFullscreen) { + element.msRequestFullscreen(); + } + }-*/; + +} diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/IconResources.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/IconResources.java new file mode 100755 index 00000000000..dd1a3dcf9ec --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/IconResources.java @@ -0,0 +1,13 @@ +package com.sap.sse.gwt.client; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.ImageResource; + +public interface IconResources extends ClientBundle { + + public static final IconResources INSTANCE = GWT.create(IconResources.class); + + @Source("images/remove.png") + ImageResource removeIcon(); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/GenericListBox.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/GenericListBox.java similarity index 96% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/GenericListBox.java rename to java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/GenericListBox.java index 86925fdce37..621b5d494a3 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/GenericListBox.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/GenericListBox.java @@ -1,4 +1,4 @@ -package com.sap.sailing.gwt.ui.common.client; +package com.sap.sse.gwt.client.controls; import java.util.ArrayList; import java.util.Collection; diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/dialog/DataEntryDialog.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/dialog/DataEntryDialog.java index fdc9f4bb7fe..33d5e0caa06 100755 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/dialog/DataEntryDialog.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/dialog/DataEntryDialog.java @@ -30,6 +30,8 @@ import com.google.gwt.user.client.ui.TextArea; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.datepicker.client.DateBox; +import com.sap.sse.gwt.client.controls.GenericListBox; +import com.sap.sse.gwt.client.controls.GenericListBox.ValueBuilder; import com.sap.sse.gwt.client.controls.IntegerBox; /** @@ -417,6 +419,21 @@ public abstract class DataEntryDialog { result.setWordWrap(false); return result; } + + public GenericListBox createGenericListBox(ValueBuilder valueBuilder, + boolean isMultipleSelect) { + GenericListBox result = new GenericListBox<>(valueBuilder); + result.setMultipleSelect(isMultipleSelect); + result.addChangeHandler(new ChangeHandler() { + @Override + public void onChange(ChangeEvent event) { + validate(); + } + }); + DialogUtils.linkEnterToButton(getOkButton(), result); + DialogUtils.linkEscapeToButton(getCancelButton(), result); + return result; + } public ListBox createListBox(boolean isMultipleSelect) { ListBox result = new ListBox(); diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/filestorage/FileStorageManagementGwtService.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/filestorage/FileStorageManagementGwtService.java index a9c41231077..0ad38080e53 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/filestorage/FileStorageManagementGwtService.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/filestorage/FileStorageManagementGwtService.java @@ -1,5 +1,6 @@ package com.sap.sse.gwt.client.filestorage; +import java.io.IOException; import java.util.Map; import com.google.gwt.user.client.rpc.RemoteService; @@ -20,7 +21,7 @@ public interface FileStorageManagementGwtService extends RemoteService { /** * @throws NoCorrespondingServiceRegisteredException service may have disappeared from registry in the meantime */ - FileStorageServicePropertyErrorsDTO testFileStorageServiceProperties(String serviceName, String localeInfoName) throws NoCorrespondingServiceRegisteredException; + FileStorageServicePropertyErrorsDTO testFileStorageServiceProperties(String serviceName, String localeInfoName) throws NoCorrespondingServiceRegisteredException, IOException; /** * @throws NoCorrespondingServiceRegisteredException service may have disappeared from registry in the meantime diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/resources/VerticalTabLayoutPanel.css b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/panels/VerticalTabLayoutPanel.css similarity index 100% rename from java/com.sap.sse.gwt/src/com/sap/sse/gwt/resources/VerticalTabLayoutPanel.css rename to java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/panels/VerticalTabLayoutPanel.css diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/panels/VerticalTabLayoutPanel.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/panels/VerticalTabLayoutPanel.java index c30cf12450e..c9c9a767128 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/panels/VerticalTabLayoutPanel.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/panels/VerticalTabLayoutPanel.java @@ -175,7 +175,7 @@ public class VerticalTabLayoutPanel extends ResizeComposite implements HasWidget } interface VerticalTabLayoutPanelResources extends ClientBundle { - @Source("com/sap/sse/gwt/resources/VerticalTabLayoutPanel.css") + @Source("VerticalTabLayoutPanel.css") CssResource verticalTabPanelStyles(); } diff --git a/java/com.sap.sse.mail.replication.test/.classpath b/java/com.sap.sse.mail.replication.test/.classpath new file mode 100755 index 00000000000..eca7bdba8f0 --- /dev/null +++ b/java/com.sap.sse.mail.replication.test/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/java/com.sap.sse.mail.replication.test/.project b/java/com.sap.sse.mail.replication.test/.project new file mode 100755 index 00000000000..51fa2fe50fb --- /dev/null +++ b/java/com.sap.sse.mail.replication.test/.project @@ -0,0 +1,28 @@ + + + com.sap.sse.mail.replication.test + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/java/com.sap.sse.mail.replication.test/.settings/org.eclipse.jdt.core.prefs b/java/com.sap.sse.mail.replication.test/.settings/org.eclipse.jdt.core.prefs new file mode 100755 index 00000000000..0c68a61dca8 --- /dev/null +++ b/java/com.sap.sse.mail.replication.test/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/java/com.sap.sse.mail.replication.test/.settings/org.eclipse.pde.core.prefs b/java/com.sap.sse.mail.replication.test/.settings/org.eclipse.pde.core.prefs new file mode 100755 index 00000000000..f29e940a005 --- /dev/null +++ b/java/com.sap.sse.mail.replication.test/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/java/com.sap.sse.mail.replication.test/META-INF/MANIFEST.MF b/java/com.sap.sse.mail.replication.test/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..2757ce82521 --- /dev/null +++ b/java/com.sap.sse.mail.replication.test/META-INF/MANIFEST.MF @@ -0,0 +1,13 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Test +Bundle-SymbolicName: com.sap.sse.mail.replication.test +Bundle-Version: 1.0.0.qualifier +Bundle-Vendor: SAP +Fragment-Host: com.sap.sse.mail +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Require-Bundle: org.hamcrest;bundle-version="1.1.0", + org.junit4;bundle-version="4.8.2", + com.rabbitmq.client, + com.sap.sse.replication.testsupport, + com.sap.sse.mail.replication.testsupport diff --git a/java/com.sap.sse.mail.replication.test/build.properties b/java/com.sap.sse.mail.replication.test/build.properties new file mode 100755 index 00000000000..34d2e4d2dad --- /dev/null +++ b/java/com.sap.sse.mail.replication.test/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/java/com.sap.sse.mail.replication.test/pom.xml b/java/com.sap.sse.mail.replication.test/pom.xml new file mode 100755 index 00000000000..eb287b53335 --- /dev/null +++ b/java/com.sap.sse.mail.replication.test/pom.xml @@ -0,0 +1,13 @@ + + + 4.0.0 + + root + com.sap.sailing + 1.0.0-SNAPSHOT + + com.sap.sse.mail.replication.test + eclipse-plugin + diff --git a/java/com.sap.sse.mail.replication.test/src/com/sap/sse/mail/replication/test/SimpleMailServiceReplicationTest.java b/java/com.sap.sse.mail.replication.test/src/com/sap/sse/mail/replication/test/SimpleMailServiceReplicationTest.java new file mode 100755 index 00000000000..7692ee59b1e --- /dev/null +++ b/java/com.sap.sse.mail.replication.test/src/com/sap/sse/mail/replication/test/SimpleMailServiceReplicationTest.java @@ -0,0 +1,25 @@ +package com.sap.sse.mail.replication.test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +import org.junit.Test; + +import com.sap.sse.common.mail.MailException; +import com.sap.sse.mail.replication.testsupport.AbstractMailServiceReplicationTest; + +public class SimpleMailServiceReplicationTest extends AbstractMailServiceReplicationTest { + @Test + public void testReplicationOfSendMail() throws InterruptedException, + IllegalAccessException, MailException { + master.sendMail("test@ing.me", "oh what a beatiful mail", "to be sent on this day"); + + replicaReplicator.waitUntilQueueIsEmpty(); + Thread.sleep(3000); + + assertThat("mail was not sent on replica", + AbstractMailServiceReplicationTest.numberOfMailsSent.get(replica), equalTo(null)); + assertThat("mail was sent on master", + AbstractMailServiceReplicationTest.numberOfMailsSent.get(master), equalTo(1)); + } +} diff --git a/java/com.sap.sse.mail.replication.testsupport/.classpath b/java/com.sap.sse.mail.replication.testsupport/.classpath new file mode 100755 index 00000000000..eca7bdba8f0 --- /dev/null +++ b/java/com.sap.sse.mail.replication.testsupport/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/java/com.sap.sse.mail.replication.testsupport/.project b/java/com.sap.sse.mail.replication.testsupport/.project new file mode 100755 index 00000000000..1d1e9965612 --- /dev/null +++ b/java/com.sap.sse.mail.replication.testsupport/.project @@ -0,0 +1,28 @@ + + + com.sap.sse.mail.replication.testsupport + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/java/com.sap.sse.mail.replication.testsupport/.settings/org.eclipse.jdt.core.prefs b/java/com.sap.sse.mail.replication.testsupport/.settings/org.eclipse.jdt.core.prefs new file mode 100755 index 00000000000..0c68a61dca8 --- /dev/null +++ b/java/com.sap.sse.mail.replication.testsupport/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/java/com.sap.sse.mail.replication.testsupport/.settings/org.eclipse.pde.core.prefs b/java/com.sap.sse.mail.replication.testsupport/.settings/org.eclipse.pde.core.prefs new file mode 100755 index 00000000000..f29e940a005 --- /dev/null +++ b/java/com.sap.sse.mail.replication.testsupport/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/java/com.sap.sse.mail.replication.testsupport/META-INF/MANIFEST.MF b/java/com.sap.sse.mail.replication.testsupport/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..84cf686c18a --- /dev/null +++ b/java/com.sap.sse.mail.replication.testsupport/META-INF/MANIFEST.MF @@ -0,0 +1,17 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Test +Bundle-SymbolicName: com.sap.sse.mail.replication.testsupport +Bundle-Version: 1.0.0.qualifier +Bundle-Vendor: SAP +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Require-Bundle: com.sap.sse.replication, + com.sap.sse.replication.testsupport, + com.sap.sse.mail, + com.sap.sse, + com.sap.sse.common, + org.hamcrest;bundle-version="1.1.0", + org.junit4;bundle-version="4.8.2", + org.mockito.mockito-core;bundle-version="1.10.14", + com.sun.mail.javax.mail;bundle-version="1.4.5" +Export-Package: com.sap.sse.mail.replication.testsupport diff --git a/java/com.sap.sse.mail.replication.testsupport/build.properties b/java/com.sap.sse.mail.replication.testsupport/build.properties new file mode 100755 index 00000000000..34d2e4d2dad --- /dev/null +++ b/java/com.sap.sse.mail.replication.testsupport/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/java/com.sap.sse.mail.replication.testsupport/pom.xml b/java/com.sap.sse.mail.replication.testsupport/pom.xml new file mode 100755 index 00000000000..2ed85a10b24 --- /dev/null +++ b/java/com.sap.sse.mail.replication.testsupport/pom.xml @@ -0,0 +1,13 @@ + + + 4.0.0 + + root + com.sap.sailing + 1.0.0-SNAPSHOT + + com.sap.sse.mail.replication.testsupport + eclipse-plugin + diff --git a/java/com.sap.sse.mail.replication.testsupport/src/com/sap/sse/mail/replication/testsupport/AbstractMailServiceReplicationTest.java b/java/com.sap.sse.mail.replication.testsupport/src/com/sap/sse/mail/replication/testsupport/AbstractMailServiceReplicationTest.java new file mode 100644 index 00000000000..835afedebab --- /dev/null +++ b/java/com.sap.sse.mail.replication.testsupport/src/com/sap/sse/mail/replication/testsupport/AbstractMailServiceReplicationTest.java @@ -0,0 +1,59 @@ +package com.sap.sse.mail.replication.testsupport; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; + +import com.sap.sse.common.mail.MailException; +import com.sap.sse.mail.MailService; +import com.sap.sse.mail.impl.MailServiceImpl; +import com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp; +import com.sap.sse.replication.testsupport.AbstractServerWithSingleServiceReplicationTest; + +public class AbstractMailServiceReplicationTest extends AbstractServerWithSingleServiceReplicationTest { + public AbstractMailServiceReplicationTest() { + super(new MailServerReplicationTestSetUp()); + } + + public static final Map numberOfMailsSent = new HashMap<>(); + + @Before + public void clearNumberOfMailsSent() { + numberOfMailsSent.clear(); + } + + public static MailServiceImpl createMailCountingService(final boolean canSendMail) { + return new MailServiceImpl(null) { + @Override + protected void internalSendMail(String toAddress, String subject, ContentSetter contentSetter) + throws MailException { + if (canSendMail) { + Integer old = numberOfMailsSent.get(this); + numberOfMailsSent.put(this, old == null ? 1 : old + 1); + } + } + }; + } + + public static class MailServerReplicationTestSetUp extends AbstractServerReplicationTestSetUp { + public MailServerReplicationTestSetUp() { + super(); + } + + public MailServerReplicationTestSetUp(int servletPort) { + super(servletPort); + } + + @Override + protected MailServiceImpl createNewMaster() throws MailException { + return createMailCountingService(true); + } + + @Override + protected MailServiceImpl createNewReplica() throws MailException { + return createMailCountingService(false); + } + } + +} diff --git a/java/com.sap.sse.mail/.classpath b/java/com.sap.sse.mail/.classpath new file mode 100755 index 00000000000..b862a296d38 --- /dev/null +++ b/java/com.sap.sse.mail/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/java/com.sap.sse.mail/.project b/java/com.sap.sse.mail/.project new file mode 100755 index 00000000000..05362453cda --- /dev/null +++ b/java/com.sap.sse.mail/.project @@ -0,0 +1,28 @@ + + + com.sap.sse.mail + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/java/com.sap.sse.mail/.settings/org.eclipse.jdt.core.prefs b/java/com.sap.sse.mail/.settings/org.eclipse.jdt.core.prefs new file mode 100755 index 00000000000..295926d9641 --- /dev/null +++ b/java/com.sap.sse.mail/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/java/com.sap.sse.mail/.settings/org.eclipse.pde.core.prefs b/java/com.sap.sse.mail/.settings/org.eclipse.pde.core.prefs new file mode 100755 index 00000000000..b7e72d019c2 --- /dev/null +++ b/java/com.sap.sse.mail/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/java/com.sap.sse.mail/META-INF/MANIFEST.MF b/java/com.sap.sse.mail/META-INF/MANIFEST.MF new file mode 100755 index 00000000000..d5aff706f24 --- /dev/null +++ b/java/com.sap.sse.mail/META-INF/MANIFEST.MF @@ -0,0 +1,17 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Sse Email +Bundle-SymbolicName: com.sap.sse.mail +Bundle-Version: 1.0.0.qualifier +Bundle-Vendor: SAP +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Bundle-Activator: com.sap.sse.mail.impl.Activator +Export-Package: com.sap.sse.mail, + com.sap.sse.mail.impl +Import-Package: javax.mail, + javax.mail.internet, + org.osgi.framework +Require-Bundle: com.sap.sse.replication, + com.sap.sse.operationaltransformation, + com.sap.sse.common, + com.sap.sse diff --git a/java/com.sap.sse.mail/build.properties b/java/com.sap.sse.mail/build.properties new file mode 100755 index 00000000000..41eb6ade2b4 --- /dev/null +++ b/java/com.sap.sse.mail/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/java/com.sap.sse.mail/pom.xml b/java/com.sap.sse.mail/pom.xml new file mode 100644 index 00000000000..5d7f50f4093 --- /dev/null +++ b/java/com.sap.sse.mail/pom.xml @@ -0,0 +1,12 @@ + + + 4.0.0 + + root + com.sap.sailing + 1.0.0-SNAPSHOT + + com.sap.sse.mail + eclipse-plugin + diff --git a/java/com.sap.sse.mail/src/com/sap/sse/mail/MailOperation.java b/java/com.sap.sse.mail/src/com/sap/sse/mail/MailOperation.java new file mode 100644 index 00000000000..908e751563a --- /dev/null +++ b/java/com.sap.sse.mail/src/com/sap/sse/mail/MailOperation.java @@ -0,0 +1,15 @@ +package com.sap.sse.mail; + +import com.sap.sse.mail.impl.ReplicableMailService; +import com.sap.sse.replication.OperationWithResult; + +public interface MailOperation extends OperationWithResult { + /** + * By default, the mail-related operations will perform an update on the receiving replica that in turn + * will trigger replication of this change transitively. + */ + default boolean isRequiresExplicitTransitiveReplication() { + return false; + } + +} diff --git a/java/com.sap.sse.mail/src/com/sap/sse/mail/MailService.java b/java/com.sap.sse.mail/src/com/sap/sse/mail/MailService.java new file mode 100644 index 00000000000..06017a4c737 --- /dev/null +++ b/java/com.sap.sse.mail/src/com/sap/sse/mail/MailService.java @@ -0,0 +1,32 @@ +package com.sap.sse.mail; + +import javax.mail.Multipart; + +import com.sap.sse.common.mail.MailException; +import com.sap.sse.mail.impl.ReplicableMailService; +import com.sap.sse.replication.Replicable; +import com.sap.sse.replication.impl.ReplicableWithObjectInputStream; +import com.sap.sse.util.ClearStateTestSupport; + + +/** + * Announced via OSGi service registry. Requires {@code mail.properties} file in {@code configuration} directory + * to be correctly configured. + * + * A mail should only be sent out once by one instance. This is similar to only adding an item once to a store + * (database). Therefore - while a sendMail() operation is replicated - it is only executed by one instance, + * for now the master instance. All replicas receive an empty ({@code null}) mail configuration during + * {@link Replicable#initiallyFillFrom initial replication}, and therefore do not try to send mails themselves. + * + * @author Axel Uhl + * @author Fredrik Teschke + * + */ +public interface MailService extends ReplicableWithObjectInputStream>, ClearStateTestSupport { + void sendMail(String toAddress, String subject, String body) throws MailException; + + /** + * Send mail with multipart content (e.g. inline image). + */ + void sendMail(String toAddress, String subject, Multipart multipartContent) throws MailException; +} diff --git a/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/Activator.java b/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/Activator.java new file mode 100644 index 00000000000..45b6b3e46ec --- /dev/null +++ b/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/Activator.java @@ -0,0 +1,56 @@ +package com.sap.sse.mail.impl; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; + +import com.sap.sse.mail.MailService; +import com.sap.sse.replication.Replicable; +import com.sap.sse.util.ClearStateTestSupport; + +public class Activator implements BundleActivator { + private static final Logger logger = Logger.getLogger(Activator.class.getName()); + + private ServiceRegistration registration; + + public void start(BundleContext context) throws Exception { + // Load mail properties + final String jettyHome = System.getProperty("jetty.home", "configuration"); + final File propertiesDir = new File(jettyHome).getParentFile(); + File propertiesfile = new File(propertiesDir, "mail.properties"); + Properties mailProperties = new Properties(); + try { + mailProperties.load(new FileReader(propertiesfile)); + } catch (IOException ioe) { + logger.log(Level.SEVERE, "Couldn't read mail properties from " + propertiesfile.getCanonicalPath(), ioe); + } + + MailService mailService = new MailServiceImpl(mailProperties); + registration = context.registerService(MailService.class, mailService, null); + final Dictionary replicableServiceProperties = new Hashtable<>(); + replicableServiceProperties.put(Replicable.OSGi_Service_Registry_ID_Property_Name, mailService.getId() + .toString()); + context.registerService(Replicable.class.getName(), mailService, replicableServiceProperties); + context.registerService(ClearStateTestSupport.class.getName(), mailService, null); + Logger.getLogger(Activator.class.getName()).info("Mail Service registered."); + } + + /* + * (non-Javadoc) + * + * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext) + */ + public void stop(BundleContext bundleContext) throws Exception { + registration.unregister(); + } + +} diff --git a/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java b/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java new file mode 100644 index 00000000000..e46bab2d984 --- /dev/null +++ b/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java @@ -0,0 +1,226 @@ +package com.sap.sse.mail.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.net.MalformedURLException; +import java.util.HashSet; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.mail.Message.RecipientType; +import javax.mail.MessagingException; +import javax.mail.Multipart; +import javax.mail.PasswordAuthentication; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; + +import com.sap.sse.common.mail.MailException; +import com.sap.sse.replication.OperationExecutionListener; +import com.sap.sse.replication.OperationWithResult; +import com.sap.sse.replication.ReplicationMasterDescriptor; +import com.sap.sse.replication.impl.OperationWithResultWithIdWrapper; + +public class MailServiceImpl implements ReplicableMailService { + private static final Logger logger = Logger.getLogger(MailServiceImpl.class.getName()); + + private Properties mailProperties; + + /** + * The master from which this replicable is currently replicating, or null if this replicable is not + * currently replicated from any master. + */ + private ReplicationMasterDescriptor replicatingFromMaster; + private final ConcurrentHashMap, OperationExecutionListener> operationExecutionListeners; + private final Set> operationsSentToMasterForReplication = new HashSet<>(); + private ThreadLocal currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster = ThreadLocal + .withInitial(() -> false); + + public MailServiceImpl(Properties mailProperties) { + this.mailProperties = mailProperties; + this.operationExecutionListeners = new ConcurrentHashMap<>(); + } + + private class SMTPAuthenticator extends javax.mail.Authenticator { + public PasswordAuthentication getPasswordAuthentication() { + String username = mailProperties.getProperty("mail.smtp.user"); + String password = mailProperties.getProperty("mail.smtp.password"); + return new PasswordAuthentication(username, password); + } + } + + protected static interface ContentSetter { + void setContent(MimeMessage msg) throws MessagingException; + } + + private boolean canSendMail() { + return mailProperties != null && mailProperties.containsKey("mail.transport.protocol"); + } + + //protected for testing purposes + protected void internalSendMail(String toAddress, String subject, ContentSetter contentSetter) throws MailException { + if (canSendMail()) { + if (toAddress != null) { + Session session = Session.getInstance(mailProperties, new SMTPAuthenticator()); + MimeMessage msg = new MimeMessage(session); + ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); + try { + msg.setFrom(new InternetAddress(mailProperties.getProperty("mail.from", "root@sapsailing.com"))); + msg.setSubject(subject); + msg.addRecipient(RecipientType.TO, new InternetAddress(toAddress.trim())); + + // this fixes the DCH MIME type error + // see http://tanyamadurapperuma.blogspot.de/2014/01/struggling-with-nosuchproviderexception.html + Thread.currentThread().setContextClassLoader(javax.mail.Session.class.getClassLoader()); + + contentSetter.setContent(msg); + + // for testing with gmail + //Transport ts = session.getTransport("smtps"); + + Transport ts = session.getTransport(); + ts.connect(); + ts.sendMessage(msg, msg.getRecipients(RecipientType.TO)); + ts.close(); + logger.info("mail sent to " + toAddress + " with subject " + subject); + } catch (MessagingException e) { + logger.log(Level.SEVERE, "Error trying to send mail to " + toAddress, e); + throw new MailException(e.getMessage(), e); + } finally { + Thread.currentThread().setContextClassLoader(oldClassLoader); + } + } + } else { + logger.warning("No mail properties provided. Cannot send e-mail about " + + subject + + " to " + + toAddress + + ". This could also mean that this is running on a replica server in which case this is perfectly fine."); + } + } + + @Override + public Void internalSendMail(String toAddress, String subject, String body) throws MailException { + internalSendMail(toAddress, subject, new ContentSetter() { + @Override + public void setContent(MimeMessage msg) throws MessagingException { + msg.setContent(body, "text/plain"); + } + }); + return null; + } + + @Override + public void sendMail(String toAddress, String subject, String body) throws MailException { + apply(s -> s.internalSendMail(toAddress, subject, body)); + } + + @Override + public Void internalSendMail(String toAddress, String subject, Multipart multipartContent) throws MailException { + internalSendMail(toAddress, subject, new ContentSetter() { + @Override + public void setContent(MimeMessage msg) throws MessagingException { + msg.setContent(multipartContent); + } + }); + return null; + } + + @Override + public void sendMail(String toAddress, String subject, Multipart multipartContent) throws MailException { + apply(s -> s.internalSendMail(toAddress, subject, multipartContent)); + } + + @Override + public void clearState() throws Exception { + // nothing to clear for test support + } + + // ----------------- Replication ------------- + @Override + public void clearReplicaState() throws MalformedURLException, IOException, InterruptedException { + // do nothing + } + + @Override + public Serializable getId() { + return getClass().getName(); + } + + @Override + public ObjectInputStream createObjectInputStreamResolvingAgainstCache(InputStream is) throws IOException { + return new ObjectInputStream(is); + } + + @Override + public void initiallyFillFromInternal(ObjectInputStream is) throws IOException, ClassNotFoundException, + InterruptedException { + // send mails only from master + mailProperties = null; + } + + @Override + public void serializeForInitialReplicationInternal(ObjectOutputStream objectOutputStream) throws IOException { + // do nothing + } + + @Override + public Iterable> getOperationExecutionListeners() { + return operationExecutionListeners.keySet(); + } + + @Override + public void addOperationExecutionListener(OperationExecutionListener listener) { + operationExecutionListeners.put(listener, listener); + } + + @Override + public void removeOperationExecutionListener(OperationExecutionListener listener) { + operationExecutionListeners.remove(listener); + } + + @Override + public ReplicationMasterDescriptor getMasterDescriptor() { + return replicatingFromMaster; + } + + @Override + public void startedReplicatingFrom(ReplicationMasterDescriptor master) { + this.replicatingFromMaster = master; + } + + @Override + public void stoppedReplicatingFrom(ReplicationMasterDescriptor master) { + this.replicatingFromMaster = null; + } + + @Override + public void addOperationSentToMasterForReplication( + OperationWithResultWithIdWrapper operationWithResultWithIdWrapper) { + this.operationsSentToMasterForReplication.add(operationWithResultWithIdWrapper); + } + + @Override + public boolean hasSentOperationToMaster(OperationWithResult operation) { + return this.operationsSentToMasterForReplication.remove(operation); + } + + @Override + public boolean isCurrentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster() { + return currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster.get(); + } + + @Override + public void setCurrentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster( + boolean currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster) { + this.currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster + .set(currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster); + } +} diff --git a/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/ReplicableMailService.java b/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/ReplicableMailService.java new file mode 100644 index 00000000000..116ae1f798c --- /dev/null +++ b/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/ReplicableMailService.java @@ -0,0 +1,12 @@ +package com.sap.sse.mail.impl; + +import javax.mail.Multipart; + +import com.sap.sse.common.mail.MailException; +import com.sap.sse.mail.MailService; + +public interface ReplicableMailService extends MailService { + Void internalSendMail(String toAddress, String subject, String body) throws MailException; + + Void internalSendMail(String toAddress, String subject, Multipart multipartContent) throws MailException; +} diff --git a/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerReplicationTest.java b/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerReplicationTestSetUp.java old mode 100755 new mode 100644 similarity index 91% rename from java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerReplicationTest.java rename to java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerReplicationTestSetUp.java index 001315e97a8..912a95e821b --- a/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerReplicationTest.java +++ b/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerReplicationTestSetUp.java @@ -22,8 +22,6 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.GZIPOutputStream; -import org.junit.After; -import org.junit.Before; import org.junit.Rule; import org.junit.rules.Timeout; @@ -42,16 +40,26 @@ import com.sap.sse.replication.impl.ReplicationReceiver; import com.sap.sse.replication.impl.ReplicationServiceImpl; import com.sap.sse.replication.impl.SingletonReplicablesProvider; -public abstract class AbstractServerReplicationTest, ReplicableImpl extends ReplicableInterface> { - private static final Logger logger = Logger.getLogger(AbstractServerReplicationTest.class.getName()); +public abstract class AbstractServerReplicationTestSetUp, ReplicableImpl extends ReplicableInterface> { + private static final Logger logger = Logger.getLogger(AbstractServerReplicationTestSetUp.class.getName()); - protected static final int SERVLET_PORT = 9990; + protected static final int DEFAULT_SERVLET_PORT = 9990; + + private final int servletPort; protected ReplicableImpl replica; protected ReplicableImpl master; protected ReplicationServiceTestImpl replicaReplicator; private ReplicaDescriptor replicaDescriptor; private ReplicationServiceImpl masterReplicator; - protected ReplicationMasterDescriptor masterDescriptor; + protected ReplicationMasterDescriptor masterDescriptor; + + protected AbstractServerReplicationTestSetUp() { + servletPort = DEFAULT_SERVLET_PORT; + } + + protected AbstractServerReplicationTestSetUp(int servletPort) { + this.servletPort = servletPort; + } @Rule public Timeout AbstractTracTracLiveTestTimeout = new Timeout(5 * 60 * 1000); // timeout after 5 minutes private Thread initialLoadTestServerThread; @@ -64,7 +72,6 @@ public abstract class AbstractServerReplicationTest, ReplicationMasterDescriptor> result = basicSetUp( @@ -120,9 +127,9 @@ public abstract class AbstractServerReplicationTest replicaReplicator = new ReplicationServiceTestImpl(exchangeName, exchangeHost, rim, replicaDescriptor, - this.replica, this.master, masterReplicator, masterDescriptor); + this.replica, this.master, masterReplicator, masterDescriptor, servletPort); Pair, ReplicationMasterDescriptor> result = new Pair<>(replicaReplicator, masterDescriptor); logger.info("starting initial load transmission servlet for "+getClass().getName()); initialLoadTestServerThread = replicaReplicator.startInitialLoadTransmissionServlet(); @@ -136,16 +143,15 @@ public abstract class AbstractServerReplicationTest getReplicaReplicator() { + return replicaReplicator; + } + + public ReplicationMasterDescriptor getMasterDescriptor() { + return masterDescriptor; + } } diff --git a/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerWithMultipleServicesReplicationTest.java b/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerWithMultipleServicesReplicationTest.java new file mode 100644 index 00000000000..125c97f6dee --- /dev/null +++ b/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerWithMultipleServicesReplicationTest.java @@ -0,0 +1,25 @@ +package com.sap.sse.replication.testsupport; + +import java.util.HashSet; +import java.util.Set; + +import org.junit.After; +import org.junit.Before; + +public abstract class AbstractServerWithMultipleServicesReplicationTest { + protected final Set> testSetUps = new HashSet<>(); + + @Before + public void setUp() throws Exception { + for (AbstractServerReplicationTestSetUp testSetUp : testSetUps) { + testSetUp.setUp(); + } + } + + @After + public void tearDown() throws Exception { + for (AbstractServerReplicationTestSetUp testSetUp : testSetUps) { + testSetUp.tearDown(); + } + } +} diff --git a/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerWithSingleServiceReplicationTest.java b/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerWithSingleServiceReplicationTest.java new file mode 100755 index 00000000000..2e20ed3ee1c --- /dev/null +++ b/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerWithSingleServiceReplicationTest.java @@ -0,0 +1,63 @@ +package com.sap.sse.replication.testsupport; + +import java.io.IOException; + +import org.junit.After; +import org.junit.Before; + +import com.sap.sse.common.Util.Pair; +import com.sap.sse.replication.Replicable; +import com.sap.sse.replication.ReplicationMasterDescriptor; +import com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp.ReplicationServiceTestImpl; + +public abstract class AbstractServerWithSingleServiceReplicationTest, ReplicableImpl extends ReplicableInterface> { + protected final AbstractServerReplicationTestSetUp testSetUp; + protected ReplicableImpl replica; + protected ReplicableImpl master; + protected ReplicationServiceTestImpl replicaReplicator; + protected ReplicationMasterDescriptor masterDescriptor; + + + public AbstractServerWithSingleServiceReplicationTest(AbstractServerReplicationTestSetUp testSetUp) { + this.testSetUp = testSetUp; + } + + @Before + public void setUp() throws Exception { + testSetUp.setUp(); + master = testSetUp.getMaster(); + replica = testSetUp.getReplica(); + replicaReplicator = testSetUp.getReplicaReplicator(); + masterDescriptor = testSetUp.getMasterDescriptor(); + } + + protected Pair, ReplicationMasterDescriptor> basicSetUp(boolean dropDB, + ReplicableImpl master, ReplicableImpl replica) throws Exception { + return testSetUp.basicSetUp(dropDB, master, replica); + } + + @After + public void tearDown() throws Exception { + testSetUp.tearDown(); + } + + protected ReplicableImpl createNewMaster() throws Exception { + return testSetUp.createNewMaster(); + } + + protected ReplicableImpl createNewReplica() throws Exception { + return testSetUp.createNewReplica(); + } + + protected void persistenceSetUp(boolean dropDB) { + testSetUp.persistenceSetUp(dropDB); + } + + protected void persistenceTearDown() { + testSetUp.persistenceTearDown(); + } + + public void stopReplicatingToMaster() throws IOException { + testSetUp.stopReplicatingToMaster(); + } +} diff --git a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/DefaultPermissions.java b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/DefaultPermissions.java index 79b1928472e..dbc47ea78c8 100755 --- a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/DefaultPermissions.java +++ b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/DefaultPermissions.java @@ -1,15 +1,15 @@ package com.sap.sse.security.shared; -public enum DefaultPermissions { +public enum DefaultPermissions implements Permission { ALL_EVENTS("event:*"); - private DefaultPermissions(String permissionname) { - this.permissionname = permissionname; + private DefaultPermissions(String stringPermission) { + this.stringPermission = stringPermission; } - public String getPermissionname() { - return permissionname; + public String getStringPermission() { + return stringPermission; } - private final String permissionname; + private final String stringPermission; } diff --git a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/DefaultRoles.java b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/DefaultRoles.java index 8afd245f2ba..56e676ac6f9 100755 --- a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/DefaultRoles.java +++ b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/DefaultRoles.java @@ -1,12 +1,13 @@ package com.sap.sse.security.shared; -public enum DefaultRoles { +public enum DefaultRoles implements Role { ADMIN("admin"); private DefaultRoles(String rolename) { this.rolename = rolename; } + @Override public String getRolename() { return rolename; } diff --git a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/Permission.java b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/Permission.java new file mode 100755 index 00000000000..d2f9695650b --- /dev/null +++ b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/Permission.java @@ -0,0 +1,9 @@ +package com.sap.sse.security.shared; + +public interface Permission { + String name(); + + int ordinal(); + + String getStringPermission(); +} diff --git a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/PermissionsForRoleProvider.java b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/PermissionsForRoleProvider.java new file mode 100755 index 00000000000..11cc02e9a1c --- /dev/null +++ b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/PermissionsForRoleProvider.java @@ -0,0 +1,13 @@ +package com.sap.sse.security.shared; + + +/** + * Roles can imply permissions. For example, an application may define that the role "admin" can do everything by + * returning a "*" permission from {@link #getPermissions(String)}. + * + * @author Axel Uhl (D043530) + * + */ +public interface PermissionsForRoleProvider { + Iterable getPermissions(String role); +} diff --git a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/Role.java b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/Role.java new file mode 100755 index 00000000000..a238aecb09f --- /dev/null +++ b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/Role.java @@ -0,0 +1,9 @@ +package com.sap.sse.security.shared; + +public interface Role { + String getRolename(); + + String name(); + + int ordinal(); +} diff --git a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/WildcardPermission.java b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/WildcardPermission.java new file mode 100755 index 00000000000..aacd4f30936 --- /dev/null +++ b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/WildcardPermission.java @@ -0,0 +1,264 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.sap.sse.security.shared; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * A WildcardPermission is a very flexible permission construct supporting multiple levels of + * permission matching. However, most people will probably follow some standard conventions as explained below. + *

      + *

      Simple Usage

      + *

      + * In the simplest form, WildcardPermission can be used as a simple permission string. You could grant a + * user an "editNewsletter" permission and then check to see if the user has the editNewsletter + * permission by calling + *

      + * subject.isPermitted("editNewsletter") + *

      + * This is (mostly) equivalent to + *

      + * subject.isPermitted( new WildcardPermission("editNewsletter") ) + *

      + * but more on that later. + *

      + * The simple permission string may work for simple applications, but it requires you to have permissions like + * "viewNewsletter", "deleteNewsletter", + * "createNewsletter", etc. You can also grant a user "*" permissions + * using the wildcard character (giving this class its name), which means they have all permissions. But + * using this approach there's no way to just say a user has "all newsletter permissions". + *

      + * For this reason, WildcardPermission supports multiple levels of permissioning. + *

      + *

      Multiple Levels

      + *

      + * WildcardPermission also supports the concept of multiple levels. For example, you could + * restructure the previous simple example by granting a user the permission "newsletter:edit". + * The colon in this example is a special character used by the WildcardPermission that delimits the + * next token in the permission. + *

      + * In this example, the first token is the domain that is being operated on + * and the second token is the action being performed. Each level can contain multiple values. So you + * could simply grant a user the permission "newsletter:view,edit,create" which gives them + * access to perform view, edit, and create actions in the newsletter + * domain. Then you could check to see if the user has the "newsletter:create" + * permission by calling + *

      + * subject.isPermitted("newsletter:create") + *

      + * (which would return true). + *

      + * In addition to granting multiple permissions via a single string, you can grant all permission for a particular + * level. So if you wanted to grant a user all actions in the newsletter domain, you could simply give + * them "newsletter:*". Now, any permission check for "newsletter:XXX" + * will return true. It is also possible to use the wildcard token at the domain level (or both): so you + * could grant a user the "view" action across all domains "*:view". + *

      + *

      Instance-level Access Control

      + *

      + * Another common usage of the WildcardPermission is to model instance-level Access Control Lists. + * In this scenario you use three tokens - the first is the domain, the second is the action, and + * the third is the instance you are acting on. + *

      + * So for example you could grant a user "newsletter:edit:12,13,18". In this example, assume + * that the third token is the system's ID of the newsletter. That would allow the user to edit newsletters + * 12, 13, and 18. This is an extremely powerful way to express permissions, + * since you can now say things like "newsletter:*:13" (grant a user all actions for newsletter + * 13), "newsletter:view,create,edit:*" (allow the user to + * view, create, or edit any newsletter), or + * "newsletter:*:* (allow the user to perform any action on any newsletter). + *

      + * To perform checks against these instance-level permissions, the application should include the instance ID in the + * permission check like so: + *

      + * subject.isPermitted( "newsletter:edit:13" ) + *

      + * There is no limit to the number of tokens that can be used, so it is up to your imagination in terms of ways that + * this could be used in your application. However, the Shiro team likes to standardize some common usages shown + * above to help people get started and provide consistency in the Shiro community.

      + * + * This class has been slightly adjusted from the original Shiro version such that it no longer depends on any Shiro + * classes but still offers the same services that the Shiro WildcardPermission does. This way, the + * permission can also be used by a GWT client and an Android implementation. + */ +public class WildcardPermission implements Serializable { + + //TODO - JavaDoc methods + + /** + * + */ + private static final long serialVersionUID = -7136806951296823464L; + /*-------------------------------------------- + | C O N S T A N T S | + ============================================*/ + protected static final String WILDCARD_TOKEN = "*"; + protected static final String PART_DIVIDER_TOKEN = ":"; + protected static final String SUBPART_DIVIDER_TOKEN = ","; + protected static final boolean DEFAULT_CASE_SENSITIVE = false; + + /*-------------------------------------------- + | I N S T A N C E V A R I A B L E S | + ============================================*/ + private List> parts; + + /*-------------------------------------------- + | C O N S T R U C T O R S | + ============================================*/ + /** + * Default no-arg constructor for subclasses only - end-user developers instantiating Permission instances must + * provide a wildcard string at a minimum, since Permission instances are immutable once instantiated. + *

      + * Note that the WildcardPermission class is very robust and typically subclasses are not necessary unless you + * wish to create type-safe Permission objects that would be used in your application, such as perhaps a + * {@code UserPermission}, {@code SystemPermission}, {@code PrinterPermission}, etc. If you want such type-safe + * permission usage, consider subclassing the {@link DomainPermission DomainPermission} class for your needs. + */ + protected WildcardPermission() { + } + + public WildcardPermission(String wildcardString) { + this(wildcardString, DEFAULT_CASE_SENSITIVE); + } + + public WildcardPermission(String wildcardString, boolean caseSensitive) { + setParts(wildcardString, caseSensitive); + } + + protected void setParts(String wildcardString) { + setParts(wildcardString, DEFAULT_CASE_SENSITIVE); + } + + protected void setParts(String wildcardString, boolean caseSensitive) { + if (wildcardString == null || wildcardString.trim().length() == 0) { + throw new IllegalArgumentException("Wildcard string cannot be null or empty. Make sure permission strings are properly formatted."); + } + + wildcardString = wildcardString.trim(); + + List parts = Arrays.asList(wildcardString.split(PART_DIVIDER_TOKEN)); + + this.parts = new ArrayList>(); + for (String part : parts) { + Set subparts = new HashSet<>(Arrays.asList(part.split(SUBPART_DIVIDER_TOKEN))); + if (!caseSensitive) { + subparts = lowercase(subparts); + } + if (subparts.isEmpty()) { + throw new IllegalArgumentException("Wildcard string cannot contain parts with only dividers. Make sure permission strings are properly formatted."); + } + this.parts.add(subparts); + } + + if (this.parts.isEmpty()) { + throw new IllegalArgumentException("Wildcard string cannot contain only dividers. Make sure permission strings are properly formatted."); + } + } + + private Set lowercase(Set subparts) { + Set lowerCasedSubparts = new LinkedHashSet(subparts.size()); + for (String subpart : subparts) { + lowerCasedSubparts.add(subpart.toLowerCase()); + } + return lowerCasedSubparts; + } + + /*-------------------------------------------- + | A C C E S S O R S / M O D I F I E R S | + ============================================*/ + protected List> getParts() { + return this.parts; + } + + /*-------------------------------------------- + | M E T H O D S | + ============================================*/ + + public boolean implies(WildcardPermission wp) { + // By default only supports comparisons with other WildcardPermissions + List> otherParts = wp.getParts(); + + int i = 0; + for (Set otherPart : otherParts) { + // If this permission has fewer parts than the other permission, everything after the number of parts contained + // in this permission is automatically implied, so return true + if (getParts().size() - 1 < i) { + return true; + } else { + Set part = getParts().get(i); + if (!part.contains(WILDCARD_TOKEN) && !part.containsAll(otherPart)) { + return false; + } + i++; + } + } + + // If this permission has more parts than the other parts, only imply it if all of the other parts are wildcards + for (; i < getParts().size(); i++) { + Set part = getParts().get(i); + if (!part.contains(WILDCARD_TOKEN)) { + return false; + } + } + + return true; + } + + /** + * Returns a string representation that can again equivalently be parsed by + * {@link WildcardPermission#WildcardPermission(String)}. + */ + public String toString() { + StringBuilder buffer = new StringBuilder(); + for (Set part : parts) { + if (buffer.length() > 0) { + buffer.append(PART_DIVIDER_TOKEN); + } + boolean first = true; + for (String subpart : part) { + if (first) { + first = false; + } else { + buffer.append(SUBPART_DIVIDER_TOKEN); + } + buffer.append(subpart); + } + } + return buffer.toString(); + } + + public boolean equals(Object o) { + if (o instanceof WildcardPermission) { + WildcardPermission wp = (WildcardPermission) o; + return parts.equals(wp.parts); + } + return false; + } + + public int hashCode() { + return parts.hashCode(); + } + +} diff --git a/java/com.sap.sse.security.replication.test/META-INF/MANIFEST.MF b/java/com.sap.sse.security.replication.test/META-INF/MANIFEST.MF index 8945be6f73c..69111890757 100644 --- a/java/com.sap.sse.security.replication.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.security.replication.test/META-INF/MANIFEST.MF @@ -6,10 +6,14 @@ Bundle-Version: 1.0.0.qualifier Bundle-Vendor: SAP Fragment-Host: com.sap.sse.security Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Require-Bundle: org.junit4;bundle-version="4.8.2", +Require-Bundle: org.hamcrest;bundle-version="1.1.0", + org.junit4;bundle-version="4.8.2", com.sap.sse.security.userstore.mongodb, com.sap.sse.mongodb, com.rabbitmq.client, org.mongodb.mongo-java-driver;bundle-version="2.13.0", com.sap.sse.replication.testsupport, - com.sap.sse.replication.testsupport + com.sap.sse.mail.replication.testsupport, + org.objenesis;bundle-version="2.1.0", + org.mockito.mockito-core;bundle-version="1.10.14" +Import-Package: org.osgi.util.tracker;version="1.5.1" diff --git a/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/AbstractSecurityReplicationTest.java b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/AbstractSecurityReplicationTest.java index 19191993b3f..97fec10c49c 100755 --- a/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/AbstractSecurityReplicationTest.java +++ b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/AbstractSecurityReplicationTest.java @@ -2,34 +2,40 @@ package com.sap.sse.security.replication.test; import java.io.IOException; import java.net.MalformedURLException; -import java.util.Properties; import com.sap.sse.mongodb.MongoDBService; -import com.sap.sse.replication.testsupport.AbstractServerReplicationTest; +import com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp; +import com.sap.sse.replication.testsupport.AbstractServerWithSingleServiceReplicationTest; import com.sap.sse.security.SecurityService; import com.sap.sse.security.impl.SecurityServiceImpl; import com.sap.sse.security.userstore.mongodb.UserStoreImpl; -public abstract class AbstractSecurityReplicationTest extends AbstractServerReplicationTest { - private MongoDBService mongoDBService; +public abstract class AbstractSecurityReplicationTest extends AbstractServerWithSingleServiceReplicationTest { + public AbstractSecurityReplicationTest() { + super(new SecurityServerReplicationTestSetUp()); + } - @Override - protected void persistenceSetUp(boolean dropDB) { - mongoDBService = MongoDBService.INSTANCE; - if (dropDB) { - mongoDBService.getDB().dropDatabase(); + public static class SecurityServerReplicationTestSetUp extends AbstractServerReplicationTestSetUp { + private MongoDBService mongoDBService; + + @Override + protected void persistenceSetUp(boolean dropDB) { + mongoDBService = MongoDBService.INSTANCE; + if (dropDB) { + mongoDBService.getDB().dropDatabase(); + } + } + + @Override + protected SecurityServiceImpl createNewMaster() throws MalformedURLException, IOException, InterruptedException { + SecurityServiceImpl result = new SecurityServiceImpl(new UserStoreImpl()); + result.clearReplicaState(); + return result; + } + + @Override + protected SecurityServiceImpl createNewReplica() { + return new SecurityServiceImpl(new UserStoreImpl()); } } - - @Override - protected SecurityServiceImpl createNewMaster() throws MalformedURLException, IOException, InterruptedException { - SecurityServiceImpl result = new SecurityServiceImpl(new UserStoreImpl(), new Properties()); - result.clearReplicaState(); - return result; - } - - @Override - protected SecurityServiceImpl createNewReplica() { - return new SecurityServiceImpl(new UserStoreImpl(), new Properties()); - } } diff --git a/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SecurityReplicationLeadingToEmailReplicationTest.java b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SecurityReplicationLeadingToEmailReplicationTest.java new file mode 100644 index 00000000000..804ab07da17 --- /dev/null +++ b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SecurityReplicationLeadingToEmailReplicationTest.java @@ -0,0 +1,152 @@ +package com.sap.sse.security.replication.test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import java.io.IOException; +import java.net.MalformedURLException; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.osgi.util.tracker.ServiceTracker; + +import com.sap.sse.common.mail.MailException; +import com.sap.sse.mail.MailService; +import com.sap.sse.mail.impl.MailServiceImpl; +import com.sap.sse.mail.replication.testsupport.AbstractMailServiceReplicationTest; +import com.sap.sse.replication.testsupport.AbstractServerWithMultipleServicesReplicationTest; +import com.sap.sse.security.SecurityService; +import com.sap.sse.security.impl.SecurityServiceImpl; +import com.sap.sse.security.shared.UserManagementException; +import com.sap.sse.security.userstore.mongodb.UserStoreImpl; + +/** + * See if transitively triggered email operation ultimately behaves well and results in only one outbound email. + * + * @author Fredrik Teschke + */ +public class SecurityReplicationLeadingToEmailReplicationTest extends AbstractServerWithMultipleServicesReplicationTest { + private final SecurityServerReplicationTestSetUp securitySetUp = new SecurityServerReplicationTestSetUp(); + private final MailServerReplicationTestSetUp mailSetUp = new MailServerReplicationTestSetUp(); + + private MailServiceImpl masterMailService; + private MailServiceImpl replicaMailService; + + @Before + @Override + public void setUp() throws Exception { + masterMailService = AbstractMailServiceReplicationTest.createMailCountingService(true); + replicaMailService = AbstractMailServiceReplicationTest.createMailCountingService(false); + AbstractMailServiceReplicationTest.numberOfMailsSent.clear(); + super.setUp(); + } + + private class SecurityServerReplicationTestSetUp extends + AbstractSecurityReplicationTest.SecurityServerReplicationTestSetUp { + @Override + protected SecurityServiceImpl createNewMaster() throws MalformedURLException, IOException, InterruptedException { + @SuppressWarnings("unchecked") + ServiceTracker trackerMock = mock(ServiceTracker.class); + doReturn(masterMailService).when(trackerMock).getService(); + SecurityServiceImpl result = new SecurityServiceImpl(trackerMock, new UserStoreImpl()); + result.clearReplicaState(); + return result; + } + + @Override + protected SecurityServiceImpl createNewReplica() { + @SuppressWarnings("unchecked") + ServiceTracker trackerMock = mock(ServiceTracker.class); + doReturn(replicaMailService).when(trackerMock).getService(); + SecurityServiceImpl result = new SecurityServiceImpl(trackerMock, new UserStoreImpl()); + return result; + } + } + + private class MailServerReplicationTestSetUp extends AbstractMailServiceReplicationTest.MailServerReplicationTestSetUp { + private static final int SERVLET_PORT = 9991; + + public MailServerReplicationTestSetUp() { + super(SERVLET_PORT); + } + + @Override + protected MailServiceImpl createNewMaster() throws MailException { + return masterMailService; + } + + @Override + protected MailServiceImpl createNewReplica() throws MailException { + return replicaMailService; + } + } + + public SecurityReplicationLeadingToEmailReplicationTest() { + testSetUps.add(securitySetUp); + testSetUps.add(mailSetUp); + } + + /** + * See if sending an email (triggered by creating the user on the master) results in only one mail being sent + * (on the master). + */ + @Test + public void triggerEmailSendByAddingUserOnMaster() + throws UserManagementException, MailException, IllegalAccessException, InterruptedException { + //TODO IllegalStateExceptions thrown, probably because the two replication services per instance share the + //same message queue, but don't know about each other (unlike actual OSGi setup, where there is only + //one replication service per instance that nows all Replicables) + SecurityService masterSecurityService = securitySetUp.getMaster(); + + final String username = "Ernie"; + final String email = "ernie@sesame-street.com"; + final String password = "BertMyFriend"; + final String validationBaseURL = null; //so that validation email is not sent + + masterSecurityService.createSimpleUser(username, email, password, validationBaseURL); + + masterSecurityService.sendMail(username, "subject", "body"); + + securitySetUp.getReplicaReplicator().waitUntilQueueIsEmpty(); + mailSetUp.getReplicaReplicator().waitUntilQueueIsEmpty(); + Thread.sleep(3000); + + assertThat("mail was not sent on replica", + AbstractMailServiceReplicationTest.numberOfMailsSent.get(replicaMailService), equalTo(null)); + assertThat("mail was sent on master", + AbstractMailServiceReplicationTest.numberOfMailsSent.get(masterMailService), equalTo(1)); + } + + + /** + * Try the other way round - have to ignore for now, no replication test seems to try applying + * operations on the replica. + */ + @Test + @Ignore + public void triggerEmailSendByAddingUserOnReplica() + throws UserManagementException, MailException, IllegalAccessException, InterruptedException { + SecurityService replicaSecurityService = securitySetUp.getReplica(); + + final String username = "Ernie"; + final String email = "ernie@sesame-street.com"; + final String password = "BertMyFriend"; + final String validationBaseURL = null; //so that validation email is not sent + + replicaSecurityService.createSimpleUser(username, email, password, validationBaseURL); + + replicaSecurityService.sendMail(username, "subject", "body"); + + securitySetUp.getReplicaReplicator().waitUntilQueueIsEmpty(); + mailSetUp.getReplicaReplicator().waitUntilQueueIsEmpty(); + Thread.sleep(3000); + + assertThat("mail was not sent on replica", + AbstractMailServiceReplicationTest.numberOfMailsSent.get(replicaMailService), equalTo(null)); + assertThat("mail was sent on master", + AbstractMailServiceReplicationTest.numberOfMailsSent.get(masterMailService), equalTo(1)); + } +} diff --git a/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SimpleSecurityReplicationTest.java b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SimpleSecurityReplicationTest.java index 439018643f9..08b962910ce 100755 --- a/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SimpleSecurityReplicationTest.java +++ b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SimpleSecurityReplicationTest.java @@ -9,8 +9,8 @@ import static org.junit.Assert.assertTrue; import org.junit.Test; +import com.sap.sse.common.mail.MailException; import com.sap.sse.security.User; -import com.sap.sse.security.shared.MailException; import com.sap.sse.security.shared.UserManagementException; public class SimpleSecurityReplicationTest extends AbstractSecurityReplicationTest { diff --git a/java/com.sap.sse.security.test/META-INF/MANIFEST.MF b/java/com.sap.sse.security.test/META-INF/MANIFEST.MF old mode 100644 new mode 100755 index 104dfefa28e..c6c6aa543bc --- a/java/com.sap.sse.security.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.security.test/META-INF/MANIFEST.MF @@ -9,4 +9,6 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Require-Bundle: org.junit4;bundle-version="4.8.2", com.sap.sse.security.userstore.mongodb, org.mongodb.mongo-java-driver;bundle-version="2.13.0", - com.sap.sse.mongodb + com.sap.sse.mongodb, + com.sap.sse.mail +Import-Package: org.apache.shiro.session.mgt diff --git a/java/com.sap.sse.security.test/src/com/sap/sse/security/jaxrs/api/SecurityResourceTest.java b/java/com.sap.sse.security.test/src/com/sap/sse/security/jaxrs/api/SecurityResourceTest.java new file mode 100755 index 00000000000..53eb451d6fd --- /dev/null +++ b/java/com.sap.sse.security.test/src/com/sap/sse/security/jaxrs/api/SecurityResourceTest.java @@ -0,0 +1,96 @@ +package com.sap.sse.security.jaxrs.api; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.session.Session; +import org.apache.shiro.subject.Subject; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; +import org.junit.Before; +import org.junit.Test; + +import com.sap.sse.common.mail.MailException; +import com.sap.sse.security.BearerAuthenticationToken; +import com.sap.sse.security.SecurityService; +import com.sap.sse.security.User; +import com.sap.sse.security.UsernamePasswordRealm; +import com.sap.sse.security.impl.Activator; +import com.sap.sse.security.impl.SecurityServiceImpl; +import com.sap.sse.security.shared.UserManagementException; +import com.sap.sse.security.userstore.mongodb.PersistenceFactory; +import com.sap.sse.security.userstore.mongodb.UserStoreImpl; + +public class SecurityResourceTest { + private SecurityResource servlet; + private SecurityServiceImpl service; + + @Before + public void setUp() throws UserManagementException, MailException { + PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory().getDatabase().dropDatabase(); + ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + try { + final UserStoreImpl store = new UserStoreImpl(); + Activator.setTestUserStore(store); + UsernamePasswordRealm.setTestUserStore(store); + service = new SecurityServiceImpl(/* mailServiceTracker */ null, + store, /* setAsActivatorSecurityService */ true); + SecurityUtils.setSecurityManager(service.getSecurityManager()); + Session session = SecurityUtils.getSubject().getSession(); + assertNotNull(session); + servlet = new SecurityResource() { + @Override + public SecurityService getService() { + return service; + } + }; + store.addPermissionForUser("admin", "can do"); + store.addPermissionForUser("admin", "event:view:*"); + store.addPermissionForUser("admin", "event:edit:123"); + } finally { + Thread.currentThread().setContextClassLoader(oldContextClassLoader); + } + } + + private String createAccessToken() throws ParseException { + String responseJsonString = (String) servlet.respondWithAccessTokenForUser("admin").getEntity(); + JSONObject responseJson = (JSONObject) new JSONParser().parse(responseJsonString); + assertEquals("admin", responseJson.get("username")); + String accessToken = (String) responseJson.get("access_token"); + assertNotNull(accessToken); + return accessToken; + } + + @Test + public void createAccessTokenAndAuthenticate() throws ParseException { + String accessToken = createAccessToken(); + User user = service.getUserByAccessToken(accessToken); + assertNotNull(user); + assertEquals("admin", user.getName()); + final Subject subject = SecurityUtils.getSubject(); + subject.login(new BearerAuthenticationToken(accessToken)); + assertTrue(subject.isAuthenticated()); + assertEquals("admin", subject.getPrincipal()); + assertTrue(subject.isPermitted("can do")); + assertFalse(subject.isPermitted("can't do")); + assertTrue(subject.isPermitted("event:view:999")); + assertTrue(subject.isPermitted("event:edit:123")); + assertFalse(subject.isPermitted("event:edit:234")); + subject.logout(); + assertFalse(subject.isAuthenticated()); + } + + @Test + public void ensureOldBearerTokenIsInvalidatedByObtainingNewOne() throws ParseException { + String accessToken = createAccessToken(); + createAccessToken(); + User user = service.getUserByAccessToken(accessToken); + assertNull(user); // the old access token is expected to have been obsoleted by obtaining a new one + } +} diff --git a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/LoginTest.java b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/LoginTest.java old mode 100644 new mode 100755 index 71d9f9da6f1..9229f35686b --- a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/LoginTest.java +++ b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/LoginTest.java @@ -6,7 +6,6 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.net.UnknownHostException; -import java.util.Properties; import org.apache.shiro.SecurityUtils; import org.junit.Before; @@ -40,7 +39,7 @@ public class LoginTest { UsernamePasswordRealm.setTestUserStore(store); Activator.setTestUserStore(store); Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); // to enable shiro to find classes from com.sap.sse.security - new SecurityServiceImpl(store, /* mailProperties */ new Properties()); + Activator.setSecurityService(new SecurityServiceImpl(store)); } @Test diff --git a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/WildcardPermissionTest.java b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/WildcardPermissionTest.java new file mode 100755 index 00000000000..a53bed4eef6 --- /dev/null +++ b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/WildcardPermissionTest.java @@ -0,0 +1,15 @@ +package com.sap.sse.security.test; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import com.sap.sse.security.shared.WildcardPermission; + +public class WildcardPermissionTest { + @Test + public void simpleToStringParseTest() { + WildcardPermission p = new WildcardPermission("a:b,c:*"); + assertEquals(p, new WildcardPermission(p.toString())); + } +} diff --git a/java/com.sap.sse.security.ui/META-INF/MANIFEST.MF b/java/com.sap.sse.security.ui/META-INF/MANIFEST.MF index 347baaf4407..fb55d40f181 100644 --- a/java/com.sap.sse.security.ui/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.security.ui/META-INF/MANIFEST.MF @@ -21,7 +21,8 @@ Require-Bundle: javax.servlet;bundle-version="2.5.0", org.apache.shiro.core;bundle-version="1.2.2", org.apache.shiro.web;bundle-version="1.2.2", com.sap.sse.replication, - com.sap.sse.security.common + com.sap.sse.security.common, + com.sap.sse.mail Web-ContextPath: /security/ui Export-Package: com.sap.sse.security.ui.client, com.sap.sse.security.ui.client.component, diff --git a/java/com.sap.sse.security.ui/Security UI sdm.launch b/java/com.sap.sse.security.ui/Security UI sdm.launch old mode 100644 new mode 100755 index c74715a12d6..52862c5ed50 --- a/java/com.sap.sse.security.ui/Security UI sdm.launch +++ b/java/com.sap.sse.security.ui/Security UI sdm.launch @@ -4,13 +4,12 @@ + - - - + - + @@ -43,7 +42,7 @@ - + - + diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/IconResources.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/IconResources.java index 489510d0ea0..2e82d56e3bd 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/IconResources.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/IconResources.java @@ -8,36 +8,33 @@ public interface IconResources extends ClientBundle { public static final IconResources INSTANCE = GWT.create(IconResources.class); - @Source("com/sap/sse/security/ui/images/logo.png") + @Source("images/logo.png") ImageResource sapLogo(); - @Source("com/sap/sse/security/ui/images/user.png") + @Source("images/user.png") ImageResource user(); - @Source("com/sap/sse/security/ui/images/user_small.png") + @Source("images/user_small.png") ImageResource userSmall(); - @Source("com/sap/sse/security/ui/images/user_icon.png") + @Source("images/user_icon.png") ImageResource userIcon(); - @Source("com/sap/sse/security/ui/images/status_red.png") + @Source("images/status_red.png") ImageResource statusRed(); - @Source("com/sap/sse/security/ui/images/status_green.png") + @Source("images/status_green.png") ImageResource statusGreen(); - @Source("com/sap/sse/security/ui/images/status_yellow.png") + @Source("images/status_yellow.png") ImageResource statusYellow(); - @Source("com/sap/sse/security/ui/images/status_blue.png") + @Source("images/status_blue.png") ImageResource statusBlue(); - @Source("com/sap/sse/security/ui/images/status_grey.png") + @Source("images/status_grey.png") ImageResource statusGrey(); - @Source("com/sap/sse/security/ui/images/delete.png") + @Source("images/delete.png") ImageResource delete(); - - @Source("com/sap/sse/gwt/client/images/remove.png") - ImageResource remove(); } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementService.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementService.java index 6ae80791520..01a4f952a44 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementService.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementService.java @@ -4,8 +4,8 @@ import java.util.Collection; import java.util.Map; import com.google.gwt.user.client.rpc.RemoteService; +import com.sap.sse.common.mail.MailException; import com.sap.sse.security.shared.DefaultRoles; -import com.sap.sse.security.shared.MailException; import com.sap.sse.security.shared.UserManagementException; import com.sap.sse.security.ui.oauth.client.CredentialDTO; import com.sap.sse.security.ui.oauth.shared.OAuthException; diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java index 839ca489f9d..76ad400b9f8 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java @@ -22,12 +22,14 @@ import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.ImageResourceRenderer; import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.gwt.client.controls.listedit.StringListEditorComposite; import com.sap.sse.gwt.client.dialog.DataEntryDialog; import com.sap.sse.security.shared.DefaultPermissions; import com.sap.sse.security.shared.DefaultRoles; +import com.sap.sse.security.shared.PermissionsForRoleProvider; import com.sap.sse.security.shared.UserManagementException; import com.sap.sse.security.ui.client.IconResources; import com.sap.sse.security.ui.client.UserChangeEventHandler; @@ -51,11 +53,15 @@ public class UserDetailsView extends FlowPanel { private final StringListEditorComposite rolesEditor; private final StringListEditorComposite permissionsEditor; private final VerticalPanel accountPanels; + private final ListBox allPermissionsList; private UserDTO user; - public UserDetailsView(final UserService userService, UserDTO user, final StringMessages stringMessages) { + private final PermissionsForRoleProvider permissionForRoleProvider; + + public UserDetailsView(final UserService userService, UserDTO user, final StringMessages stringMessages, final UserListDataProvider userListDataProvider, PermissionsForRoleProvider permissionsForRoleProvider) { final UserManagementServiceAsync userManagementService = userService.getUserManagementService(); this.stringMessages = stringMessages; + this.permissionForRoleProvider = permissionsForRoleProvider; this.user = user; addStyleName("userDetailsView"); List defaultRoleNames = new ArrayList<>(); @@ -64,9 +70,9 @@ public class UserDetailsView extends FlowPanel { } List defaultPermissionNames = new ArrayList<>(); for (DefaultPermissions defaultPermission : DefaultPermissions.values()) { - defaultPermissionNames.add(defaultPermission.getPermissionname()); + defaultPermissionNames.add(defaultPermission.getStringPermission()); } - rolesEditor = new StringListEditorComposite(user==null?Collections.emptySet():user.getRoles(), stringMessages, IconResources.INSTANCE.remove(), defaultRoleNames, + rolesEditor = new StringListEditorComposite(user==null?Collections.emptySet():user.getRoles(), stringMessages, com.sap.sse.gwt.client.IconResources.INSTANCE.removeIcon(), defaultRoleNames, stringMessages.enterRoleName()); rolesEditor.addValueChangeHandler(new ValueChangeHandler>() { @Override @@ -85,7 +91,7 @@ public class UserDetailsView extends FlowPanel { if (!result.isSuccessful()) { Window.alert(stringMessages.errorUpdatingRoles(selectedUser.getName(), result.getMessage())); } else { - selectedUser.setRoles(newRoleList); + userListDataProvider.updateDisplays(); if (userService.getCurrentUser().getName().equals(selectedUser.getName())) { // if the current user's roles changed, update the user object in the user service and notify others userService.updateUser(/* notify other instances */ true); @@ -95,7 +101,8 @@ public class UserDetailsView extends FlowPanel { })); } }); - permissionsEditor = new StringListEditorComposite(user==null?Collections.emptySet():user.getPermissions(), stringMessages, IconResources.INSTANCE.remove(), defaultPermissionNames, + permissionsEditor = new StringListEditorComposite(user==null?Collections.emptySet():user.getStringPermissions(), stringMessages, + com.sap.sse.gwt.client.IconResources.INSTANCE.removeIcon(), defaultPermissionNames, stringMessages.enterPermissionName()); permissionsEditor.addValueChangeHandler(new ValueChangeHandler>() { @Override @@ -114,7 +121,7 @@ public class UserDetailsView extends FlowPanel { if (!result.isSuccessful()) { Window.alert(stringMessages.errorUpdatingPermissions(selectedUser.getName(), result.getMessage())); } else { - selectedUser.setPermissions(newPermissionList); + userListDataProvider.updateDisplays(); if (userService.getCurrentUser().getName().equals(selectedUser.getName())) { // if the current user's permissions changed, update the user object in the user service and notify others userService.updateUser(/* notify other instances */ true); @@ -168,6 +175,11 @@ public class UserDetailsView extends FlowPanel { this.add(decoratorPanel); this.add(rolesEditor); this.add(permissionsEditor); + this.add(new Label(stringMessages.allPermissions())); + allPermissionsList = new ListBox(); + allPermissionsList.setVisibleItemCount(10); + allPermissionsList.setEnabled(false); + this.add(allPermissionsList); updateUser(user, userManagementService); } @@ -240,7 +252,11 @@ public class UserDetailsView extends FlowPanel { accountPanels.add(accountPanelDecorator); } rolesEditor.setValue(user.getRoles(), /* fireEvents */ false); - permissionsEditor.setValue(user.getPermissions(), /* fireEvents */ false); + permissionsEditor.setValue(user.getStringPermissions(), /* fireEvents */ false); + allPermissionsList.clear(); + for (String permission : user.getAllPermissions(permissionForRoleProvider)) { + allPermissionsList.addItem(permission); + } } } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserListDataProvider.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserListDataProvider.java index e9d7b37c1ab..1b610f25013 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserListDataProvider.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserListDataProvider.java @@ -76,6 +76,5 @@ public class UserListDataProvider extends AbstractDataProvider { for (HasData hd : getDataDisplays()) { onRangeChanged(hd); } -// //TODO [D056866] SelectionChangeEvent.fire(singleSelectionModel); } } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserManagementPanel.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserManagementPanel.java index ec1f706e1bc..3e44dd457ae 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserManagementPanel.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserManagementPanel.java @@ -23,6 +23,7 @@ import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.view.client.SelectionChangeEvent; import com.google.gwt.view.client.SelectionChangeEvent.Handler; import com.google.gwt.view.client.SingleSelectionModel; +import com.sap.sse.security.shared.PermissionsForRoleProvider; import com.sap.sse.security.ui.client.UserChangeEventHandler; import com.sap.sse.security.ui.client.UserManagementServiceAsync; import com.sap.sse.security.ui.client.UserService; @@ -40,7 +41,7 @@ public class UserManagementPanel extends DockPanel { private UserListDataProvider userListDataProvider; - public UserManagementPanel(final UserService userService, final StringMessages stringMessages) { + public UserManagementPanel(final UserService userService, final StringMessages stringMessages, PermissionsForRoleProvider permissionsForRoleProvider) { final UserManagementServiceAsync userManagementService = userService.getUserManagementService(); VerticalPanel west = new VerticalPanel(); HorizontalPanel buttonPanel = new HorizontalPanel(); @@ -80,7 +81,9 @@ public class UserManagementPanel extends DockPanel { deleteButton.setEnabled(singleSelectionModel.getSelectedObject() != null); final UserList userList = new UserList(); userList.setSelectionModel(singleSelectionModel); - final UserDetailsView userDetailsView = new UserDetailsView(userService, singleSelectionModel.getSelectedObject(), stringMessages); + TextBox filterBox = new TextBox(); + userListDataProvider = new UserListDataProvider(userManagementService, filterBox); + final UserDetailsView userDetailsView = new UserDetailsView(userService, singleSelectionModel.getSelectedObject(), stringMessages, userListDataProvider, permissionsForRoleProvider); add(userDetailsView, DockPanel.CENTER); userDetailsView.addUserChangeEventHandler(new UserChangeEventHandler() { @Override @@ -96,8 +99,6 @@ public class UserManagementPanel extends DockPanel { } }); userList.setPageSize(20); - TextBox filterBox = new TextBox(); - userListDataProvider = new UserListDataProvider(userManagementService, filterBox); userListDataProvider.addDataDisplay(userList); SimplePager pager = new SimplePager(TextLocation.CENTER, false, /* fast forward step size */ 50, true); pager.setDisplay(userList); diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages.java index 8511ea6a4ef..44e0ed0b1c7 100755 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages.java @@ -82,4 +82,5 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages { String serverReplies(); String errorCommunicatingWithServer(); String editProfile(); + String allPermissions(); } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages.properties b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages.properties index 0ff97b2bf48..d94b589bbd7 100755 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages.properties +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages.properties @@ -74,4 +74,5 @@ serverError=An error occurred while attempting to contact the server. Please che remoteProcedureCall=Remote Procedure Call serverReplies=Server replies errorCommunicatingWithServer=Error communicating with server -editProfile=Edit user profile \ No newline at end of file +editProfile=Edit user profile +allPermissions=All permissions (including inferred from roles) \ No newline at end of file diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_de.properties b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_de.properties index c7140a49800..dbfce0de736 100755 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_de.properties +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_de.properties @@ -74,4 +74,5 @@ serverError=Es ist ein Fehler beim Versuch aufgetreten, den Server zu kontaktier remoteProcedureCall=Entfernter Prozeduraufruf serverReplies=Server antwortet errorCommunicatingWithServer=Fehler bei der Kommunikation mit dem Server -editProfile=Benutzerprofil bearbeiten \ No newline at end of file +editProfile=Benutzerprofil bearbeiten +allPermissions=Alle Berechtigungen (einschließlich der aus den Rollen abgeleiteten) \ No newline at end of file diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java index a04ae8a236d..c88046d4581 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java @@ -27,15 +27,14 @@ import org.osgi.util.tracker.ServiceTracker; import com.google.gwt.user.server.rpc.RemoteServiceServlet; import com.sap.sailing.domain.common.impl.NaturalComparator; import com.sap.sse.common.Util; +import com.sap.sse.common.mail.MailException; import com.sap.sse.security.Credential; import com.sap.sse.security.SecurityService; -import com.sap.sse.security.SessionUtils; import com.sap.sse.security.Social; import com.sap.sse.security.User; import com.sap.sse.security.shared.Account; import com.sap.sse.security.shared.Account.AccountType; import com.sap.sse.security.shared.DefaultRoles; -import com.sap.sse.security.shared.MailException; import com.sap.sse.security.shared.SocialUserAccount; import com.sap.sse.security.shared.UserManagementException; import com.sap.sse.security.shared.UsernamePasswordAccount; @@ -108,7 +107,8 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U public SuccessInfo login(String username, String password) { try { String redirectURL = getSecurityService().login(username, password); - return new SuccessInfo(true, "Success. Redirecting to "+redirectURL, redirectURL, createUserDTOFromUser(getSecurityService().getUserByName(username))); + return new SuccessInfo(true, "Success. Redirecting to "+redirectURL, redirectURL, + createUserDTOFromUser(getSecurityService().getUserByName(username))); } catch (UserManagementException | AuthenticationException e) { return new SuccessInfo(false, SuccessInfo.FAILED_TO_LOGIN, /* redirectURL */ null, null); } @@ -116,7 +116,7 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U @Override public SuccessInfo logout() { - logger.info("Logging out user: " + SessionUtils.loadUsername()); + logger.info("Logging out user: " + SecurityUtils.getSubject()); getSecurityService().logout(); getHttpSession().invalidate(); logger.info("Invalidated HTTP session"); @@ -166,7 +166,7 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U @Override public void updateSimpleUserEmail(String username, String newEmail, String validationBaseURL) throws UserManagementException, MailException { final Subject subject = SecurityUtils.getSubject(); - if (subject.hasRole(DefaultRoles.ADMIN.getRolename()) || username.equals(SessionUtils.loadUsername())) { + if (subject.hasRole(DefaultRoles.ADMIN.getRolename()) || username.equals(subject.getPrincipal().toString())) { getSecurityService().updateSimpleUserEmail(username, newEmail, validationBaseURL); } else { throw new UserManagementException(UserManagementException.INVALID_CREDENTIALS); @@ -272,7 +272,7 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U } } - private UserDTO createUserDTOFromUser(User user){ + private UserDTO createUserDTOFromUser(User user) { UserDTO userDTO; Map accounts = user.getAllAccounts(); List accountDTOs = new ArrayList<>(); @@ -288,9 +288,7 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U break; } } - userDTO = new UserDTO(user.getName(), user.getEmail(), user.isEmailValidated(), accountDTOs); - userDTO.addRoles(user.getRoles()); - userDTO.addPermissions(user.getPermissions()); + userDTO = new UserDTO(user.getName(), user.getEmail(), user.isEmailValidated(), accountDTOs, user.getRoles(), user.getPermissions()); return userDTO; } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/UserDTO.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/UserDTO.java index de53f0b7ffd..6d7b51d460d 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/UserDTO.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/UserDTO.java @@ -1,27 +1,37 @@ package com.sap.sse.security.ui.shared; +import java.util.ArrayList; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import com.google.gwt.user.client.rpc.IsSerializable; import com.sap.sse.common.Util; +import com.sap.sse.security.shared.PermissionsForRoleProvider; +import com.sap.sse.security.shared.WildcardPermission; public class UserDTO implements IsSerializable { private String name; private String email; private List accounts; - private Set roles = new HashSet<>(); - private Set permissions = new HashSet<>(); + private Set roles; + private Set permissions; private boolean emailValidated; UserDTO() {} // for serialization only - public UserDTO(String name, String email, boolean emailValidated, List accounts) { + public UserDTO(String name, String email, boolean emailValidated, List accounts, Iterable roles, Iterable stringPermissions) { this.name = name; this.email = email; this.emailValidated = emailValidated; this.accounts = accounts; + this.roles = new HashSet<>(); + Util.addAll(roles, this.roles); + this.permissions = new HashSet<>(); + for (String stringPermission : stringPermissions) { + this.permissions.add(new WildcardPermission(stringPermission)); + } } public String getName() { @@ -36,32 +46,62 @@ public class UserDTO implements IsSerializable { return roles.contains(role); } - public void addRoles(Iterable roles) { - Util.addAll(roles, this.roles); - } - - public void setRoles(Iterable newRoleList) { - this.roles.clear(); - Util.addAll(newRoleList, this.roles); - } - - public Iterable getPermissions() { + /** + * Returns the "raw" permissions explicitly set for this user. This does not include permissions + * inferred by any {@link PermissionsForRoleProvider} for the {@link #getRoles() roles} that this + * user has. Use {@link #getAllPermissions(PermissionsForRoleProvider)} for that. + */ + public Iterable getPermissions() { return permissions; } - public boolean hasPermission(String permission) { - return permissions.contains(permission); - } - - public void addPermissions(Iterable permissions) { - Util.addAll(permissions, this.permissions); - } - - public void setPermissions(Iterable newPermissionList) { - this.permissions.clear(); - Util.addAll(newPermissionList, this.permissions); + /** + * Same as {@link #getPermissions()}, but returning the permissions in their string representation, + * as specified by {@link WildcardPermission#toString()}. + */ + public Iterable getStringPermissions() { + List result = new ArrayList<>(); + for (WildcardPermission wp : getPermissions()) { + result.add(wp.toString()); + } + return result; } + /** + * Returns all permissions this user has, including those inferred from the user's {@link #getRoles() roles} by the + * permissionForRoleProvider and including the {@link #getStringPermissions()} that are set explicitly + * for this user. + * + * @param permissionsForRoleProvider + * may be null in which case only the {@link #getStringPermissions() explicit permissions} + * set for this user will be returned. + * @return a set of permissions with no duplicates, all in the format parsable by + * {@link WildcardPermission#WildcardPermission(String)} + */ + public Iterable getAllPermissions(PermissionsForRoleProvider permissionsForRoleProvider) { + Set result = new LinkedHashSet<>(); + Util.addAll(getStringPermissions(), result); + if (permissionsForRoleProvider != null) { + for (String role : getRoles()) { + Util.addAll(permissionsForRoleProvider.getPermissions(role), result); + } + } + return result; + } + + public boolean hasPermission(String permission, PermissionsForRoleProvider permissionsForRoleProvider) { + return hasPermission(new WildcardPermission(permission), permissionsForRoleProvider); + } + + public boolean hasPermission(WildcardPermission permission, PermissionsForRoleProvider permissionsForRoleProvider) { + for (String stringPermission : getAllPermissions(permissionsForRoleProvider)) { + if (new WildcardPermission(stringPermission).implies(permission)) { + return true; + } + } + return false; + } + public List getAccounts() { return accounts; } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/usermanagement/UserManagementEntryPoint.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/usermanagement/UserManagementEntryPoint.java index fbbf5853e6b..dab822ddd6d 100755 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/usermanagement/UserManagementEntryPoint.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/usermanagement/UserManagementEntryPoint.java @@ -33,7 +33,8 @@ public class UserManagementEntryPoint extends AbstractSecurityEntryPoint { UserManagementEntryPoint.this.user = user; } }); - UserManagementPanel userManagementPanel = new UserManagementPanel(getUserService(), getStringMessages()); + UserManagementPanel userManagementPanel = new UserManagementPanel(getUserService(), getStringMessages(), + /* permissionsForRoleProvider is null in this generic entry point */ null); center.add(new ScrollPanel(userManagementPanel), getStringMessages().users()); final SettingsPanel settingsPanel = new SettingsPanel(getUserManagementService(), getStringMessages()); center.add(new ScrollPanel(settingsPanel), getStringMessages().settings()); diff --git a/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/delete.png b/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/delete.png similarity index 100% rename from java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/delete.png rename to java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/delete.png diff --git a/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/logo.png b/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/logo.png similarity index 100% rename from java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/logo.png rename to java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/logo.png diff --git a/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/status_blue.png b/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/status_blue.png similarity index 100% rename from java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/status_blue.png rename to java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/status_blue.png diff --git a/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/status_green.png b/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/status_green.png similarity index 100% rename from java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/status_green.png rename to java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/status_green.png diff --git a/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/status_grey.png b/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/status_grey.png similarity index 100% rename from java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/status_grey.png rename to java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/status_grey.png diff --git a/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/status_red.png b/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/status_red.png similarity index 100% rename from java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/status_red.png rename to java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/status_red.png diff --git a/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/status_yellow.png b/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/status_yellow.png similarity index 100% rename from java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/status_yellow.png rename to java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/status_yellow.png diff --git a/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/user.png b/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/user.png similarity index 100% rename from java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/user.png rename to java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/user.png diff --git a/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/user_icon.png b/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/user_icon.png similarity index 100% rename from java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/user_icon.png rename to java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/user_icon.png diff --git a/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/user_small.png b/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/user_small.png similarity index 100% rename from java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/user_small.png rename to java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/user_small.png diff --git a/java/com.sap.sse.security.ui/src/main/resources/shiro.ini b/java/com.sap.sse.security.ui/src/main/resources/shiro.ini index 8e7d14f8c4b..41d41da7220 100755 --- a/java/com.sap.sse.security.ui/src/main/resources/shiro.ini +++ b/java/com.sap.sse.security.ui/src/main/resources/shiro.ini @@ -1,30 +1,69 @@ + [main] credentialsMatcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher -# base64 encoding, not hex in this example: credentialsMatcher.storedCredentialsHexEncoded = false credentialsMatcher.hashIterations = 1024 +# Realm configuration: +# -------------------- + +# Uncomment the following line to specify a bundle-specific permissions-for-role provider +# permissionsForRoleProvider = com.sap.myapp....MyAppPermissionsForRoleProvider upRealm = com.sap.sse.security.UsernamePasswordRealm upRealm.credentialsMatcher = $credentialsMatcher - +# Uncomment the following line make the upRealm aware of the permissionsForRoleProvider +# upRealm.permissionsForRoleProvider = $permissionsForRoleProvider oauthRealm = com.sap.sse.security.OAuthRealm +# Uncomment the following line make the oauthRealm aware of the permissionsForRoleProvider +# oauthRealm.permissionsForRoleProvider = $permissionsForRoleProvider +bearerTokenRealm = com.sap.sse.security.BearerTokenRealm +# Uncomment the following line make the bearerTokenRealm aware of the permissionsForRoleProvider +# bearerTokenRealm.permissionsForRoleProvider = $permissionsForRoleProvider -securityManager.realms = $upRealm, $oauthRealm - +# Putting it all together: sessionManager = com.sap.sse.security.SecurityWebSessionManager securityManager.sessionManager = $sessionManager - -authc = com.sap.sse.security.CustomFilter -authc.loginUrl = /security/ui/Login.html -authc.successUrl = /UserManagement.html - sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO securityManager.sessionManager.sessionDAO = $sessionDAO - cacheManager = com.sap.sse.security.SessionCacheManager securityManager.cacheManager = $cacheManager +# Authentication Filter Configurations +# ------------------------------------ + +# A filter that requires the user to have at least one of the roles specified as +# filter parameter. This makes it different from the default roles[...] filter +# which requires the user to have ALL of the roles specified as parameters. +anyofroles = com.sap.sse.security.AnyOfRolesFilter + +# A custom filter for GWT pages that require an authenticated user. +# Forwards unauthenticated users to the default login page. When +# addressed directly (and not based on a redirect), users who logged +# on successfully will be redirected to the UserManagement.html page +# where they can adjust their profile. +customGwt = com.sap.sse.security.CustomFilter +customGwt.loginUrl = /security/ui/Login.html +customGwt.successUrl = /UserManagement.html + +# Configuration for the default shiro HTTP form authentication filter. +# It assumes that form-based login parameters are sent to the +# /api/restsecurity/login RESTlet and forwards successful login attempts +# to the /api/restsecurity/hello service which is expected to respond +# with a JSON document containing the authenticated subject's properties. +authc.loginUrl = /api/restsecurity/login +authc.successUrl = /api/restsecurity/hello + +# This authentication filter accepts a bearer access token in the HTTP +# Authorization header field, as in +# Authorization: Bearer 1029741026501365024376093245 +# If no such bearer token is provided, the filter falls back to basic HTTP +# authentication ("Authentication: Basic ...") and then regular form-based +# authentication with POST parameters "username" and "password". +bearerToken = com.sap.sse.security.BearerTokenOrBasicOrFormAuthenticationFilter + +# Specifying filter chains for URL patterns + [urls] /Login.html = anon -/UserManagement.html = authc,roles[admin] +/UserManagement.html = customGwt,perms[manage_users] /EditProfile.html = anon diff --git a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/DomainObjectFactory.java b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/DomainObjectFactory.java index 59f4cf7a7ed..29ecf730a5d 100644 --- a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/DomainObjectFactory.java +++ b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/DomainObjectFactory.java @@ -15,4 +15,5 @@ public interface DomainObjectFactory { User loadUser(String name); Map> loadPreferences(); + } diff --git a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java index 46c3a062db5..db19c10abea 100644 --- a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java +++ b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java @@ -29,13 +29,20 @@ public class UserStoreImpl implements UserStore { private static final Logger logger = Logger.getLogger(UserStoreImpl.class.getName()); + private static final String ACCESS_TOKEN_KEY = "___access_token___"; + private String name = "MongoDB user store"; private final ConcurrentHashMap users; private final ConcurrentHashMap> usersByEmail; + private final ConcurrentHashMap usersByAccessToken; private final ConcurrentHashMap emailForUsername; private final ConcurrentHashMap settings; private final ConcurrentHashMap> settingTypes; + + /** + * Keys are the usernames, values are the key/value pairs representing the user's preferences + */ private final ConcurrentHashMap> preferences; /** @@ -48,7 +55,8 @@ public class UserStoreImpl implements UserStore { usersByEmail = new ConcurrentHashMap<>(); emailForUsername = new ConcurrentHashMap<>(); settings = new ConcurrentHashMap<>(); - settingTypes = new ConcurrentHashMap>(); + settingTypes = new ConcurrentHashMap<>(); + usersByAccessToken = new ConcurrentHashMap<>(); preferences = new ConcurrentHashMap<>(); final DomainObjectFactory domainObjectFactory = PersistenceFactory.INSTANCE.getDefaultDomainObjectFactory(); mongoObjectFactory = PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(); @@ -101,6 +109,33 @@ public class UserStoreImpl implements UserStore { } } + @Override + public boolean setAccessToken(String username, String accessToken) { + final boolean result; + final User user = getUserByName(username); + if (user == null) { + result = false; + } else { + result = true; + final String oldAccessToken = getPreference(username, ACCESS_TOKEN_KEY); + if (oldAccessToken != null) { + usersByAccessToken.remove(oldAccessToken); + } + usersByAccessToken.put(accessToken, user); + setPreference(username, ACCESS_TOKEN_KEY, accessToken); + } + return result; + } + + @Override + public void removeAccessToken(String username, String accessToken) { + User user = usersByAccessToken.remove(accessToken); + if (user != null) { + // the access token actually existed; now we need to update the preferences + unsetPreference(username, ACCESS_TOKEN_KEY); + } + } + private void addToUsersByEmail(User u) { if (u.getEmail() != null && !u.getEmail().isEmpty()) { Set set = usersByEmail.get(u.getEmail()); @@ -175,10 +210,24 @@ public class UserStoreImpl implements UserStore { @Override public User getUserByName(String name) { + final User result; if (name == null) { - return null; + result = null; + } else { + result = users.get(name); } - return users.get(name); + return result; + } + + @Override + public User getUserByAccessToken(String accessToken) { + final User result; + if (accessToken == null) { + result = null; + } else { + result = usersByAccessToken.get(accessToken); + } + return result; } @Override diff --git a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/DomainObjectFactoryImpl.java b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/DomainObjectFactoryImpl.java index 147863f9173..9f6ef354951 100644 --- a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/DomainObjectFactoryImpl.java +++ b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/DomainObjectFactoryImpl.java @@ -33,18 +33,16 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { @Override public Iterable loadAllUsers() { - ArrayList result = new ArrayList(); + ArrayList result = new ArrayList<>(); DBCollection userCollection = db.getCollection(CollectionNames.USERS.name()); - try { for (DBObject o : userCollection.find()) { result.add(loadUser(o)); } } catch (Exception e) { - logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load events."); - logger.log(Level.SEVERE, "loadAllEvents", e); + logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load users."); + logger.log(Level.SEVERE, "loadAllUsers", e); } - return result; } diff --git a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/MongoObjectFactoryImpl.java b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/MongoObjectFactoryImpl.java index fe9e9ef3feb..41ebcee5c8f 100644 --- a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/MongoObjectFactoryImpl.java +++ b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/MongoObjectFactoryImpl.java @@ -46,13 +46,12 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { dbUser.put(FieldNames.User.PERMISSIONS.name(), user.getPermissions()); usersCollection.update(query, dbUser, /* upsrt */true, /* multi */false, WriteConcern.SAFE); } - + @Override public void deleteUser(User user) { DBCollection usersCollection = db.getCollection(CollectionNames.USERS.name()); DBObject dbUser = new BasicDBObject(); dbUser.put(FieldNames.User.NAME.name(), user.getName()); - usersCollection.remove(dbUser); } diff --git a/java/com.sap.sse.security/META-INF/MANIFEST.MF b/java/com.sap.sse.security/META-INF/MANIFEST.MF index 8c44859f3d0..d7c272be079 100644 --- a/java/com.sap.sse.security/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.security/META-INF/MANIFEST.MF @@ -6,7 +6,8 @@ Bundle-Version: 1.0.0.qualifier Bundle-Activator: com.sap.sse.security.impl.Activator Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Import-Package: javax.mail;version="1.4.0", +Import-Package: com.sap.sse.util, + javax.mail;version="1.4.0", javax.mail.internet;version="1.4.0", javax.ws.rs;version="1.1.1", javax.ws.rs.core;version="1.1.1", @@ -32,6 +33,7 @@ Require-Bundle: javax.servlet;bundle-version="2.5.0", com.sap.sse.replication, com.sap.sse.operationaltransformation, com.sap.sse.security.common, - com.sap.sse + com.sap.sse, + com.sap.sse.mail Web-ContextPath: /security Export-Package: com.sap.sse.security diff --git a/java/com.sap.sse.security/WEB-INF/web.xml b/java/com.sap.sse.security/WEB-INF/web.xml index 418dbcc8b1a..42b7b94eabd 100644 --- a/java/com.sap.sse.security/WEB-INF/web.xml +++ b/java/com.sap.sse.security/WEB-INF/web.xml @@ -13,7 +13,11 @@ dirAllowed false - + + useFileMappedBuffer + false + + shiroEnvironmentClass diff --git a/java/com.sap.sse.security/build.properties b/java/com.sap.sse.security/build.properties index d74e5aa7be3..29ace3f1e22 100644 --- a/java/com.sap.sse.security/build.properties +++ b/java/com.sap.sse.security/build.properties @@ -4,4 +4,5 @@ output.. = bin/ bin.includes = META-INF/,\ .,\ WEB-INF/,\ - resources/shiro.ini + resources/shiro.ini,\ + webservices/ diff --git a/java/com.sap.sse.security/resources/shiro.ini b/java/com.sap.sse.security/resources/shiro.ini index d751a822a12..3519320d74b 100644 --- a/java/com.sap.sse.security/resources/shiro.ini +++ b/java/com.sap.sse.security/resources/shiro.ini @@ -1,33 +1,70 @@ [main] -# Use Built-in Cache Manager credentialsMatcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher - -# base64 encoding, not hex in this example: credentialsMatcher.storedCredentialsHexEncoded = false credentialsMatcher.hashIterations = 1024 +# Realm configuration: +# -------------------- + +# Uncomment the following line to specify a bundle-specific permissions-for-role provider +# permissionsForRoleProvider = com.sap.myapp....MyAppPermissionsForRoleProvider upRealm = com.sap.sse.security.UsernamePasswordRealm upRealm.credentialsMatcher = $credentialsMatcher - +# Uncomment the following line make the upRealm aware of the permissionsForRoleProvider +# upRealm.permissionsForRoleProvider = $permissionsForRoleProvider oauthRealm = com.sap.sse.security.OAuthRealm +# Uncomment the following line make the oauthRealm aware of the permissionsForRoleProvider +# oauthRealm.permissionsForRoleProvider = $permissionsForRoleProvider +bearerTokenRealm = com.sap.sse.security.BearerTokenRealm +# Uncomment the following line make the bearerTokenRealm aware of the permissionsForRoleProvider +# bearerTokenRealm.permissionsForRoleProvider = $permissionsForRoleProvider -securityManager.realms = $upRealm, $oauthRealm - +# Putting it all together: sessionManager = com.sap.sse.security.SecurityWebSessionManager securityManager.sessionManager = $sessionManager - -authc = com.sap.sse.security.CustomFilter -authc.loginUrl = /security/ui/Login.html -authc.successUrl = /UserManagement.html - sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO securityManager.sessionManager.sessionDAO = $sessionDAO - cacheManager = com.sap.sse.security.SessionCacheManager securityManager.cacheManager = $cacheManager +# Authentication Filter Configurations +# ------------------------------------ + +# A filter that requires the user to have at least one of the roles specified as +# filter parameter. This makes it different from the default roles[...] filter +# which requires the user to have ALL of the roles specified as parameters. +anyofroles = com.sap.sse.security.AnyOfRolesFilter + +# A custom filter for GWT pages that require an authenticated user. +# Forwards unauthenticated users to the default login page. When +# addressed directly (and not based on a redirect), users who logged +# on successfully will be redirected to the UserManagement.html page +# where they can adjust their profile. +customGwt = com.sap.sse.security.CustomFilter +customGwt.loginUrl = /security/ui/Login.html +customGwt.successUrl = /UserManagement.html + +# Configuration for the default shiro HTTP form authentication filter. +# It assumes that form-based login parameters are sent to the +# /api/restsecurity/login RESTlet and forwards successful login attempts +# to the /api/restsecurity/hello service which is expected to respond +# with a JSON document containing the authenticated subject's properties. +authc.loginUrl = /api/restsecurity/login +authc.successUrl = /api/restsecurity/hello + +# This authentication filter accepts a bearer access token in the HTTP +# Authorization header field, as in +# Authorization: Bearer 1029741026501365024376093245 +# If no such bearer token is provided, the filter falls back to basic HTTP +# authentication ("Authentication: Basic ...") and then regular form-based +# authentication with POST parameters "username" and "password". +bearerToken = com.sap.sse.security.BearerTokenOrBasicOrFormAuthenticationFilter + +# Specifying filter chains for URL patterns + [urls] -/api/restsecurity/login = anon +/api/restsecurity/login = authc /api/restsecurity/logout = logout -/api/restsecurity/hello = authc \ No newline at end of file +/api/restsecurity/access_token = bearerToken +/api/restsecurity/hello = bearerToken diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/AbstractUserStoreBasedRealm.java b/java/com.sap.sse.security/src/com/sap/sse/security/AbstractUserStoreBasedRealm.java index 81453ea9614..0c747d3e106 100755 --- a/java/com.sap.sse.security/src/com/sap/sse/security/AbstractUserStoreBasedRealm.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/AbstractUserStoreBasedRealm.java @@ -19,11 +19,13 @@ import org.osgi.util.tracker.ServiceTracker; import com.sap.sse.common.Util; import com.sap.sse.security.impl.Activator; +import com.sap.sse.security.shared.PermissionsForRoleProvider; import com.sap.sse.security.shared.UserManagementException; public abstract class AbstractUserStoreBasedRealm extends AuthorizingRealm { private static final Logger logger = Logger.getLogger(AbstractUserStoreBasedRealm.class.getName()); private final Future userStore; + private PermissionsForRoleProvider permissionsForRoleProvider; /** * In a non-OSGi test environment, having Shiro instantiate this class with a default constructor makes it difficult @@ -39,6 +41,7 @@ public abstract class AbstractUserStoreBasedRealm extends AuthorizingRealm { public AbstractUserStoreBasedRealm() { super(); + setCachingEnabled(false); // always grab fresh authorization info from the user store BundleContext context = Activator.getContext(); if (context != null) { userStore = createUserStoreFuture(context); @@ -46,6 +49,10 @@ public abstract class AbstractUserStoreBasedRealm extends AuthorizingRealm { userStore = null; } } + + public void setPermissionsForRoleProvider(PermissionsForRoleProvider permissionsForRoleProvider) { + this.permissionsForRoleProvider = permissionsForRoleProvider; + } private Future createUserStoreFuture(BundleContext bundleContext) { final ServiceTracker tracker = new ServiceTracker<>(bundleContext, UserStore.class, /* customizer */ null); @@ -101,6 +108,13 @@ public abstract class AbstractUserStoreBasedRealm extends AuthorizingRealm { String username = r.toString(); try { Util.addAll(getUserStore().getRolesFromUser(username), roles); + if (permissionsForRoleProvider != null) { + for (String role : roles) { + for (String permission : permissionsForRoleProvider.getPermissions(role)) { + ai.addStringPermission(permission); + } + } + } Util.addAll(getUserStore().getPermissionsFromUser(username), permissions); } catch (UserManagementException e) { throw new AuthenticationException(e.getMessage()); diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/BearerAuthenticationToken.java b/java/com.sap.sse.security/src/com/sap/sse/security/BearerAuthenticationToken.java new file mode 100755 index 00000000000..6d839cbd7df --- /dev/null +++ b/java/com.sap.sse.security/src/com/sap/sse/security/BearerAuthenticationToken.java @@ -0,0 +1,35 @@ +package com.sap.sse.security; + +import org.apache.shiro.authc.AuthenticationToken; + +import com.sap.sse.security.impl.Activator; + +/** + * An access token as issued by {@link SecurityService#createAccessToken(String)}. The user name which is returned + * by {@link #getPrincipal()} is obtained by looking up the access token using the {@link SecurityService}. + * + * @author Axel Uhl (D043530) + * + */ +public class BearerAuthenticationToken implements AuthenticationToken { + private static final long serialVersionUID = 8528031991813216585L; + private final String token; + + public BearerAuthenticationToken(String token) { + super(); + this.token = token; + } + + @Override + public Object getPrincipal() { + SecurityService securityService = Activator.getSecurityService(); + User user = securityService.getUserByAccessToken(token); + return user == null ? null : user.getName(); + } + + @Override + public String getCredentials() { + return token; + } + +} diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/BearerTokenOrBasicOrFormAuthenticationFilter.java b/java/com.sap.sse.security/src/com/sap/sse/security/BearerTokenOrBasicOrFormAuthenticationFilter.java new file mode 100755 index 00000000000..38039aba967 --- /dev/null +++ b/java/com.sap.sse.security/src/com/sap/sse/security/BearerTokenOrBasicOrFormAuthenticationFilter.java @@ -0,0 +1,65 @@ +package com.sap.sse.security; + + +import java.util.Locale; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; + +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; +import org.apache.shiro.web.filter.authc.FormAuthenticationFilter; + +import com.sap.sse.security.impl.FormAuthenticationFilterWithPublicCreateToken; +import com.sap.sse.security.jaxrs.api.SecurityResource; + +/** + * Looks for an "Authorization: Bearer <token>" HTTP header. If found, tries to authenticate a user + * with the bearer token presented. If instead an "Authorization: Basic ..." header field is found, HTTP + * basic authentication is attempted using a regular username/password token. If that isn't found, looks + * for username/password fields in the POST parameters, like the {@link FormAuthenticationFilter} does.

      + * + * This filter can be used for securing REST requests, in conjunction with the {@link SecurityResource}'s + * {@link SecurityResource#accessToken(String, String)} through which a bearer token can be obtained using + * basic authentication. + * + * @author Axel Uhl (D043530) + * + */ +public class BearerTokenOrBasicOrFormAuthenticationFilter extends BasicHttpAuthenticationFilter { + + private static final String BEARER = "Bearer"; + + /** + * Used to delegate requests to in case no bearer token and no basic authentication is found + * in the "Authorization:" header field. + */ + private final FormAuthenticationFilterWithPublicCreateToken formAuthenticationFilter; + + public BearerTokenOrBasicOrFormAuthenticationFilter() { + formAuthenticationFilter = new FormAuthenticationFilterWithPublicCreateToken(); + } + + @Override + protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) { + String authorizationHeader = getAuthzHeader(request); + if (authorizationHeader != null && !authorizationHeader.isEmpty()) { + String[] authTokens = authorizationHeader.split(" "); + if (authTokens[0].equalsIgnoreCase(BEARER)) { + return new BearerAuthenticationToken(authTokens[1]); + } else if (authTokens[0].equalsIgnoreCase(HttpServletRequest.BASIC_AUTH)) { + return super.createToken(request, response); + } + } + return formAuthenticationFilter.createToken(request, response); + } + + @Override + protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) { + String authzScheme = BEARER.toLowerCase(Locale.ENGLISH); + final String authzHeader = getAuthzHeader(request); + return (authzHeader != null && authzHeader.toLowerCase(Locale.ENGLISH).startsWith(authzScheme)) || + super.isLoginAttempt(request, response) || formAuthenticationFilter.isLoginSubmission(request, response); + } +} diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/BearerTokenRealm.java b/java/com.sap.sse.security/src/com/sap/sse/security/BearerTokenRealm.java new file mode 100755 index 00000000000..ed4d2fb22c0 --- /dev/null +++ b/java/com.sap.sse.security/src/com/sap/sse/security/BearerTokenRealm.java @@ -0,0 +1,34 @@ +package com.sap.sse.security; + +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.SaltedAuthenticationInfo; + +/** + * A realm that authenticates users by an access token which has previously been obtained through + * {@link SecurityService#createAccessToken(String)}. + * + * @author Axel Uhl (D043530) + * + */ +public class BearerTokenRealm extends AbstractUserStoreBasedRealm { + + public BearerTokenRealm() { + super(); + setAuthenticationTokenClass(BearerAuthenticationToken.class); + } + + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { + BearerAuthenticationToken accessToken = (BearerAuthenticationToken) token; + final User user = getUserStore().getUserByAccessToken(accessToken.getCredentials()); + if (user == null) { + return null; + } + // return salted credentials + SaltedAuthenticationInfo sai = new SimpleSaltedAuthenticationInfo(user.getName(), accessToken.getCredentials(), /* salt */ null); + return sai; + } + +} diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/OAuthRealm.java b/java/com.sap.sse.security/src/com/sap/sse/security/OAuthRealm.java index ea139f5f7be..0e4169c932d 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/OAuthRealm.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/OAuthRealm.java @@ -42,6 +42,7 @@ public class OAuthRealm extends AbstractUserStoreBasedRealm { public OAuthRealm() { super(); + setAuthenticationTokenClass(OAuthToken.class); } @Override @@ -184,8 +185,6 @@ public class OAuthRealm extends AbstractUserStoreBasedRealm { throw new AuthenticationException(e.getMessage()); } } - SessionUtils.saveUsername(user.getName()); - SimpleAuthenticationInfo sai = new SimpleAuthenticationInfo(); SimplePrincipalCollection spc = new SimplePrincipalCollection(); spc.add(otoken.getPrincipal(), otoken.getPrincipal().toString()); diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java b/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java index b3840233f21..c04bab7bfef 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java @@ -8,11 +8,11 @@ import javax.servlet.ServletContext; import org.apache.shiro.cache.CacheManager; import org.apache.shiro.mgt.SecurityManager; +import com.sap.sse.common.mail.MailException; import com.sap.sse.replication.impl.ReplicableWithObjectInputStream; import com.sap.sse.security.impl.ReplicableSecurityService; import com.sap.sse.security.operations.SecurityOperation; import com.sap.sse.security.shared.DefaultRoles; -import com.sap.sse.security.shared.MailException; import com.sap.sse.security.shared.SocialUserAccount; import com.sap.sse.security.shared.UserManagementException; @@ -139,4 +139,24 @@ public interface SecurityService extends ReplicableWithObjectInputStreamusername can be + * authenticated using the token. Any access token previously created for same user will be invalidated by this + * call. + * + * @return a new access token if username identifies a known user, null otherwise + */ + String createAccessToken(String username); + + /** + * Looks up a user by an access token that was created before using {@link #createAccessToken(String)} for same user name. + * + * @return null in case the access token is unknown or was deleted / invalidated + */ + User getUserByAccessToken(String accessToken); + + void removeAccessToken(String username, String accessToken); + + User loginByAccessToken(String accessToken); + } diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/SessionUtils.java b/java/com.sap.sse.security/src/com/sap/sse/security/SessionUtils.java index df20952a862..f5c56739427 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/SessionUtils.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/SessionUtils.java @@ -113,17 +113,6 @@ public class SessionUtils { session.setAttribute(SOCIAL_USER, username); } - public static String loadUsername() { - final String result; - Session session = SecurityUtils.getSubject().getSession(); - if (session == null) { - result = null; - } else { - result = (String) session.getAttribute(SOCIAL_USER); - } - return result; - } - public static void saveSessionIdToSession(String sessionId) throws AuthenticationException { Session session = SecurityUtils.getSubject().getSession(); if (session == null) { diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/SimpleSaltedAuthenticationInfo.java b/java/com.sap.sse.security/src/com/sap/sse/security/SimpleSaltedAuthenticationInfo.java index a8b8fff5a0f..79b2b428079 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/SimpleSaltedAuthenticationInfo.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/SimpleSaltedAuthenticationInfo.java @@ -9,12 +9,12 @@ import org.apache.shiro.util.SimpleByteSource; public class SimpleSaltedAuthenticationInfo implements SaltedAuthenticationInfo { private static final long serialVersionUID = -8423637970683173260L; private String username; - private String password; + private String saltedPassword; private byte[] salt; - public SimpleSaltedAuthenticationInfo(String username, String password, byte[] salt) { + public SimpleSaltedAuthenticationInfo(String username, String saltedPassword, byte[] salt) { this.username = username; - this.password = password; + this.saltedPassword = saltedPassword; this.salt = salt; } @@ -26,7 +26,7 @@ public class SimpleSaltedAuthenticationInfo implements SaltedAuthenticationInfo @Override public Object getCredentials() { - return password; + return saltedPassword; } @Override diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/UserStore.java b/java/com.sap.sse.security/src/com/sap/sse/security/UserStore.java index 4dd17e4947f..f0ad1f20670 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/UserStore.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/UserStore.java @@ -13,6 +13,8 @@ public interface UserStore extends Named { User getUserByName(String name); User getUserByEmail(String email); + + User getUserByAccessToken(String accessToken); User createUser(String name, String email, Account... accounts) throws UserManagementException; @@ -87,4 +89,14 @@ public interface UserStore extends Named { */ void replaceContentsFrom(UserStore newUserStore); + /** + * Stores an access token that can be used to authenticate the user identified by username. + * If there is no user by that name, calling this method has no effect and it will return false. + * + * @return whether a user could be identified by username + */ + boolean setAccessToken(String username, String accessToken); + + void removeAccessToken(String username, String accessToken); + } diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/UsernamePasswordRealm.java b/java/com.sap.sse.security/src/com/sap/sse/security/UsernamePasswordRealm.java index 722cdd52e16..c265e6c7ef1 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/UsernamePasswordRealm.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/UsernamePasswordRealm.java @@ -13,6 +13,7 @@ public class UsernamePasswordRealm extends AbstractUserStoreBasedRealm { public UsernamePasswordRealm() { super(); + setAuthenticationTokenClass(UsernamePasswordToken.class); } @Override @@ -41,7 +42,7 @@ public class UsernamePasswordRealm extends AbstractUserStoreBasedRealm { String saltedPassword = null; byte[] salt = null; User user = getUserStore().getUserByName(username); - if (user == null){ + if (user == null) { return null; } UsernamePasswordAccount upa = (UsernamePasswordAccount) user.getAccount(AccountType.USERNAME_PASSWORD); diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/impl/Activator.java b/java/com.sap.sse.security/src/com/sap/sse/security/impl/Activator.java old mode 100644 new mode 100755 index f8c54dfdbe5..a4e6375a4e3 --- a/java/com.sap.sse.security/src/com/sap/sse/security/impl/Activator.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/impl/Activator.java @@ -1,11 +1,7 @@ package com.sap.sse.security.impl; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; import java.util.Dictionary; import java.util.Hashtable; -import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; @@ -14,11 +10,13 @@ import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; import org.osgi.util.tracker.ServiceTracker; +import com.sap.sse.mail.MailService; import com.sap.sse.replication.Replicable; import com.sap.sse.security.SecurityService; import com.sap.sse.security.UserStore; import com.sap.sse.security.UsernamePasswordRealm; import com.sap.sse.util.ClearStateTestSupport; +import com.sap.sse.util.ServiceTrackerFactory; public class Activator implements BundleActivator { private static final Logger logger = Logger.getLogger(Activator.class.getName()); @@ -42,6 +40,10 @@ public class Activator implements BundleActivator { UsernamePasswordRealm.setTestUserStore(theTestUserStore); } + public static void setSecurityService(SecurityService securityService) { + Activator.securityService = securityService; + } + public static BundleContext getContext() { return context; } @@ -57,25 +59,16 @@ public class Activator implements BundleActivator { * registered as an OSGi service. */ public void start(BundleContext bundleContext) throws Exception { - // Load mail properties - final String jettyHome = System.getProperty("jetty.home", "configuration"); - final File propertiesDir = new File(jettyHome).getParentFile(); - File propertiesfile = new File(propertiesDir, "security.properties"); - Properties mailProperties = new Properties(); - try { - mailProperties.load(new FileReader(propertiesfile)); - } catch (IOException ioe) { - logger.log(Level.SEVERE, "Couldn't read security properties from "+propertiesfile.getCanonicalPath(), ioe); - } + context = bundleContext; if (testUserStore != null) { - createAndRegisterSecurityService(testUserStore, mailProperties); + createAndRegisterSecurityService(bundleContext, testUserStore); } else { - waitForUserStoreService(bundleContext, mailProperties); + waitForUserStoreService(bundleContext); } } - private void createAndRegisterSecurityService(UserStore store, Properties mailProperties) { - securityService = new SecurityServiceImpl(store, mailProperties); + private void createAndRegisterSecurityService(BundleContext bundleContext, UserStore store) { + securityService = new SecurityServiceImpl(ServiceTrackerFactory.createAndOpen(context, MailService.class), store, /* setAsActivatorSecurityService */ true); registration = context.registerService(SecurityService.class.getName(), securityService, null); final Dictionary replicableServiceProperties = new Hashtable<>(); @@ -85,7 +78,7 @@ public class Activator implements BundleActivator { Logger.getLogger(Activator.class.getName()).info("Security Service registered."); } - private void waitForUserStoreService(BundleContext bundleContext, final Properties mailProperties) { + private void waitForUserStoreService(BundleContext bundleContext) { context = bundleContext; final ServiceTracker tracker = new ServiceTracker<>(bundleContext, UserStore.class, /* customizer */ null); tracker.open(); @@ -97,7 +90,7 @@ public class Activator implements BundleActivator { logger.info("Waiting for UserStore service..."); UserStore userStore = tracker.waitForService(0); logger.info("Obtained UserStore service "+userStore); - createAndRegisterSecurityService(userStore, mailProperties); + createAndRegisterSecurityService(bundleContext, userStore); } catch (InterruptedException e) { logger.log(Level.SEVERE, "Interrupted while waiting for UserStore service", e); } diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/impl/FormAuthenticationFilterWithPublicCreateToken.java b/java/com.sap.sse.security/src/com/sap/sse/security/impl/FormAuthenticationFilterWithPublicCreateToken.java new file mode 100755 index 00000000000..2ab2706e1fe --- /dev/null +++ b/java/com.sap.sse.security/src/com/sap/sse/security/impl/FormAuthenticationFilterWithPublicCreateToken.java @@ -0,0 +1,20 @@ +package com.sap.sse.security.impl; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.web.filter.authc.FormAuthenticationFilter; + +public class FormAuthenticationFilterWithPublicCreateToken extends FormAuthenticationFilter { + + @Override + public AuthenticationToken createToken(ServletRequest request, ServletResponse response) { + return super.createToken(request, response); + } + + @Override + public boolean isLoginSubmission(ServletRequest request, ServletResponse response) { + return super.isLoginSubmission(request, response); + } +} diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/impl/ReplicableSecurityService.java b/java/com.sap.sse.security/src/com/sap/sse/security/impl/ReplicableSecurityService.java index e248a93c404..b51e3094c1c 100755 --- a/java/com.sap.sse.security/src/com/sap/sse/security/impl/ReplicableSecurityService.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/impl/ReplicableSecurityService.java @@ -19,6 +19,10 @@ public interface ReplicableSecurityService extends SecurityService { Void internalSetPreference(String username, String key, String value); Void internalUnsetPreference(String username, String key); + + Void internalSetAccessToken(String username, String accessToken); + + Void internalRemoveAccessToken(String username, String accessToken); Boolean internalSetSetting(String key, Object setting); diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java old mode 100644 new mode 100755 index db50118e4bc..be86335c4c1 --- a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java @@ -13,20 +13,12 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; -import java.util.Properties; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; -import javax.mail.Message.RecipientType; -import javax.mail.MessagingException; -import javax.mail.PasswordAuthentication; -import javax.mail.Session; -import javax.mail.Transport; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeMessage; import javax.servlet.Filter; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; @@ -54,6 +46,7 @@ import org.apache.shiro.web.filter.mgt.FilterChainResolver; import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver; import org.apache.shiro.web.util.SavedRequest; import org.apache.shiro.web.util.WebUtils; +import org.osgi.util.tracker.ServiceTracker; import org.scribe.builder.ServiceBuilder; import org.scribe.builder.api.FacebookApi; import org.scribe.builder.api.FlickrApi; @@ -71,10 +64,13 @@ import org.scribe.oauth.OAuthService; import com.google.gwt.user.server.rpc.RemoteServiceServlet; import com.sap.sse.common.Util; +import com.sap.sse.common.mail.MailException; +import com.sap.sse.mail.MailService; import com.sap.sse.replication.OperationExecutionListener; import com.sap.sse.replication.OperationWithResult; import com.sap.sse.replication.ReplicationMasterDescriptor; import com.sap.sse.replication.impl.OperationWithResultWithIdWrapper; +import com.sap.sse.security.BearerAuthenticationToken; import com.sap.sse.security.ClientUtils; import com.sap.sse.security.Credential; import com.sap.sse.security.GithubApi; @@ -89,7 +85,6 @@ import com.sap.sse.security.User; import com.sap.sse.security.UserStore; import com.sap.sse.security.shared.Account.AccountType; import com.sap.sse.security.shared.DefaultRoles; -import com.sap.sse.security.shared.MailException; import com.sap.sse.security.shared.SocialUserAccount; import com.sap.sse.security.shared.UserManagementException; import com.sap.sse.security.shared.UsernamePasswordAccount; @@ -111,7 +106,7 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica private final ReplicatingCacheManager cacheManager; private UserStore store; - private final Properties mailProperties; + private final ServiceTracker mailServiceTracker; private final ConcurrentHashMap, OperationExecutionListener> operationExecutionListeners; /** @@ -129,18 +124,37 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica shiroConfiguration = new Ini(); shiroConfiguration.loadFromPath("classpath:shiro.ini"); } + + public SecurityServiceImpl(UserStore store) { + this(null, store); + } /** * @param mailProperties must not be null */ - public SecurityServiceImpl(UserStore store, Properties mailProperties) { - assert mailProperties != null; - logger.info("Initializing Security Service with user store " + store+" and mail properties "+mailProperties); + public SecurityServiceImpl(ServiceTracker mailServiceTracker, UserStore store) { + this(mailServiceTracker, store, /* setAsActivatorTestSecurityService */ false); + } + + /** + * @param setAsActivatorSecurityService + * when true, the {@link Activator#setSecurityService(com.sap.sse.security.SecurityService)} + * will be called with this new instance as argument so that the cache manager can already be accessed + * when the security manager is created. {@link ReplicatingCacheManager#getCache(String)} fetches the + * activator's security service and passes it to the cache entries created. They need it, in turn, for + * replication. + * + */ + public SecurityServiceImpl(ServiceTracker mailServiceTracker, UserStore store, boolean setAsActivatorSecurityService) { + logger.info("Initializing Security Service with user store " + store); + if (setAsActivatorSecurityService) { + Activator.setSecurityService(this); + } operationsSentToMasterForReplication = new HashSet<>(); cacheManager = new ReplicatingCacheManager(); this.operationExecutionListeners = new ConcurrentHashMap<>(); this.store = store; - this.mailProperties = mailProperties; + this.mailServiceTracker = mailServiceTracker; // Create default users if no users exist yet. initEmptyStore(); Factory factory = new WebIniSecurityManagerFactory(shiroConfiguration); @@ -188,45 +202,24 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica } } } - - private class SMTPAuthenticator extends javax.mail.Authenticator { - public PasswordAuthentication getPasswordAuthentication() { - String username = mailProperties.getProperty("mail.smtp.user"); - String password = mailProperties.getProperty("mail.smtp.password"); - return new PasswordAuthentication(username, password); - } + + private MailService getMailService() { + return mailServiceTracker == null ? null : mailServiceTracker.getService(); } @Override public void sendMail(String username, String subject, String body) throws MailException { - if (this.mailProperties != null && this.mailProperties.containsKey("mail.transport.protocol")) { - final User user = getUserByName(username); - if (user != null) { - final String toAddress = user.getEmail(); - if (toAddress != null) { - Session session = Session.getInstance(this.mailProperties, new SMTPAuthenticator()); - MimeMessage msg = new MimeMessage(session); - try { - msg.setFrom(new InternetAddress(mailProperties.getProperty("mail.from", "root@sapsailing.com"))); - msg.setSubject(subject); - msg.setContent(body, "text/plain"); - msg.addRecipient(RecipientType.TO, new InternetAddress(toAddress.trim())); - Transport ts = session.getTransport(); - ts.connect(); - ts.sendMessage(msg, msg.getRecipients(RecipientType.TO)); - ts.close(); - logger.info("mail sent to user " + username + " with e-mail address " + toAddress - + " with subject " + subject); - } catch (MessagingException e) { - logger.log(Level.SEVERE, "Error trying to send mail to user " + username - + " with e-mail address " + toAddress, e); - throw new MailException(e.getMessage()); - } + final User user = getUserByName(username); + if (user != null) { + final String toAddress = user.getEmail(); + if (toAddress != null) { + MailService mailService = getMailService(); + if (mailService == null) { + logger.warning(String.format("Could not send mail to user %s: no MailService found", username)); + } else { + getMailService().sendMail(username, subject, body); } } - } else { - logger.warning("No mail properties provided. Cannot send e-mail about "+subject+" to user "+username+ - ". This could also mean that this is running on a replica server in which case this is perfectly fine."); } } @@ -284,7 +277,6 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica logger.info("Trying to login: " + username); Subject subject = SecurityUtils.getSubject(); subject.login(token); - SessionUtils.saveUsername(username); HttpServletRequest httpRequest = WebUtils.getHttpRequest(subject); SavedRequest savedRequest = WebUtils.getSavedRequest(httpRequest); if (savedRequest != null) { @@ -295,6 +287,21 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica logger.info("Redirecturl: " + redirectUrl); return redirectUrl; } + + @Override + public User loginByAccessToken(String accessToken) { + BearerAuthenticationToken token = new BearerAuthenticationToken(accessToken); + logger.info("Trying to login with access token"); + Subject subject = SecurityUtils.getSubject(); + try { + subject.login(token); + final String username = (String) token.getPrincipal(); + return store.getUserByName(username); + } catch (AuthenticationException e) { + logger.log(Level.INFO, "Authentication failed with access token "+accessToken); + throw e; + } + } @Override public void logout() { @@ -307,6 +314,11 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica public User getUserByName(String name) { return store.getUserByName(name); } + + @Override + public User getUserByAccessToken(String accessToken) { + return store.getUserByAccessToken(accessToken); + } @Override public User getUserByEmail(String email) { @@ -583,7 +595,7 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica if (!subject.isAuthenticated()) { try { subject.login(otoken); - logger.info("User [" + SessionUtils.loadUsername() + "] logged in successfully."); + logger.info("User [" + subject.getPrincipal().toString() + "] logged in successfully."); } catch (UnknownAccountException uae) { logger.info("There is no user with username of " + subject.getPrincipal()); throw new UserManagementException("Invalid credentials!"); @@ -599,7 +611,7 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica throw new UserManagementException("An error occured while authenticating the user!"); } } - String username = SessionUtils.loadUsername(); + String username = subject.getPrincipal().toString(); if (username == null) { logger.info("Something went wrong while authneticating, check doGetAuthenticationInfo() in " + OAuthRealm.class.getName() + "."); @@ -617,10 +629,10 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica public User getCurrentUser() { final User result; Subject subject = SecurityUtils.getSubject(); - if (subject == null) { + if (subject == null || !subject.isAuthenticated()) { result = null; } else { - String username = SessionUtils.loadUsername(); + String username = subject.getPrincipal().toString(); if (username == null || username.length() <= 0) { result = null; } else { @@ -852,17 +864,17 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica } @Override - public CacheManager getCacheManager() { + public ReplicatingCacheManager getCacheManager() { return cacheManager; } @Override public void setPreference(final String username, final String key, final String value) { final Subject subject = SecurityUtils.getSubject(); - if (subject.hasRole(DefaultRoles.ADMIN.name()) || username.equals(SessionUtils.loadUsername())) { + if (subject.hasRole(DefaultRoles.ADMIN.name()) || username.equals(subject.getPrincipal().toString())) { apply(s->s.internalSetPreference(username, key, value)); } else { - throw new SecurityException("User " + SessionUtils.loadUsername() + throw new SecurityException("User " + subject.getPrincipal().toString() + " does not have permission to set preference for user " + username); } } @@ -876,10 +888,10 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica @Override public void unsetPreference(String username, String key) { Subject subject = SecurityUtils.getSubject(); - if (subject.hasRole(DefaultRoles.ADMIN.name()) || username.equals(SessionUtils.loadUsername())) { + if (subject.hasRole(DefaultRoles.ADMIN.name()) || username.equals(subject.getPrincipal().toString())) { apply(s->s.internalUnsetPreference(username, key)); } else { - throw new SecurityException("User " + SessionUtils.loadUsername() + throw new SecurityException("User " + subject.getPrincipal().toString() + " does not have permission to unset preference for user " + username); } } @@ -890,21 +902,52 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica return null; } + @Override + public Void internalSetAccessToken(String username, String accessToken) { + store.setAccessToken(username, accessToken); + return null; + } + + @Override + public Void internalRemoveAccessToken(String username, String accessToken) { + store.removeAccessToken(username, accessToken); + return null; + } + @Override public String getPreference(String username, String key) { Subject subject = SecurityUtils.getSubject(); - if (subject.hasRole(DefaultRoles.ADMIN.name()) || username.equals(SessionUtils.loadUsername())) { + if (subject.hasRole(DefaultRoles.ADMIN.name()) || username.equals(subject.getPrincipal().toString())) { return store.getPreference(username, key); } else { - throw new SecurityException("User " + SessionUtils.loadUsername() + throw new SecurityException("User " + subject.getPrincipal().toString() + " does not have permission to read preferences of user " + username); } } + + @Override + public String createAccessToken(String username) { + User user = getUserByName(username); + final String token; + if (user != null) { + RandomNumberGenerator rng = new SecureRandomNumberGenerator(); + byte[] salt = rng.nextBytes().getBytes(); + token = hashPassword(new String(rng.nextBytes().getBytes()), salt); + apply(s -> s.internalSetAccessToken(user.getName(), token)); + } else { + token = null; + } + return token; + } + + @Override + public void removeAccessToken(String username, String accessToken) { + apply(s -> s.internalRemoveAccessToken(username, accessToken)); + } // ----------------- Replication ------------- @Override public void clearReplicaState() throws MalformedURLException, IOException, InterruptedException { - mailProperties.clear(); store.clear(); } diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java index a2199c495bd..cb83d6e4086 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java @@ -1,53 +1,76 @@ package com.sap.sse.security.jaxrs.api; -import java.util.logging.Logger; - -import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.Status; + +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.Subject; +import org.json.simple.JSONObject; import com.sap.sse.security.jaxrs.AbstractSecurityResource; -import com.sap.sse.security.shared.UserManagementException; @Path("/restsecurity") public class SecurityResource extends AbstractSecurityResource { - private static final Logger logger = Logger.getLogger(SecurityResource.class.getName()); - - private Response getSecurityErrorResponse(String msg) { - return Response.status(Status.BAD_REQUEST).entity(msg).type(MediaType.TEXT_PLAIN).build(); - } - + /** + * Can be used to figure out the current subject. Accepts the GET method. If the subject is + * authenticated, the service will respond with a "Hello <subjectname>" message, otherwise + * with a generic "Hello!". + */ @GET @Path("/hello") @Produces("application/json;charset=UTF-8") public Response sayHello() { - return Response.ok("Hello!", MediaType.TEXT_PLAIN).build(); + return doSayHello(); } - + + private Response doSayHello() { + final Subject subject = SecurityUtils.getSubject(); + final JSONObject result = new JSONObject(); + result.put("principal", subject.getPrincipal().toString()); + result.put("authenticated", subject.isAuthenticated()); + result.put("remembered", subject.isRemembered()); + return Response.ok(result.toJSONString(), MediaType.APPLICATION_JSON_TYPE).build(); + } + + /** + * Can be used to figure out the current subject. Accepts the POST method. If the subject is + * authenticated, the service will respond with a "Hello <subjectname>" message, otherwise + * with a generic "Hello!". + */ @POST - @Path("/login") - @Produces("application/json;charset=UTF-8") - public Response login(@FormParam("username") String username, @FormParam("password") String password) { - try { - getService().login(username, password); - logger.info("Successfully logged in " + username + " with password"); - } catch (UserManagementException e) { - logger.info("Logging in " + username + " with password failed: "+e.getMessage()); - return getSecurityErrorResponse(e.getMessage()); - } - return Response.ok("Logged in!", MediaType.TEXT_PLAIN).build(); + @Path("/hello") + @Produces("text/plain;charset=UTF-8") + public Response sayHelloPost() { + return doSayHello(); } - + @GET - @Path("/logout") - public Response logout() { - getService().logout(); - return Response.ok("Logged out", MediaType.TEXT_PLAIN).build(); + @Path("/access_token") + @Produces("application/json;charset=UTF-8") + public Response accessToken() { + return respondWithAccessTokenForAuthenticatedSubject(); } -} - \ No newline at end of file + + @POST + @Path("/access_token") + @Produces("application/json;charset=UTF-8") + public Response accessTokenPost() { + return respondWithAccessTokenForAuthenticatedSubject(); + } + + private Response respondWithAccessTokenForAuthenticatedSubject() { + final String username = SecurityUtils.getSubject().getPrincipal().toString(); + return respondWithAccessTokenForUser(username); + } + + Response respondWithAccessTokenForUser(final String username) { + JSONObject response = new JSONObject(); + response.put("username", username); + response.put("access_token", getService().createAccessToken(username)); + return Response.ok(response.toJSONString(), MediaType.APPLICATION_JSON_TYPE).build(); + } +} \ No newline at end of file diff --git a/java/com.sap.sse.security/webservices/api/access_token.html b/java/com.sap.sse.security/webservices/api/access_token.html new file mode 100755 index 00000000000..e8eadadfd30 --- /dev/null +++ b/java/com.sap.sse.security/webservices/api/access_token.html @@ -0,0 +1,60 @@ + + + + + + + + SAP Sponsorships Engine Security Webservices API + + +

      SAP Sponsorships Engine Security Webservices API

      +

      URL: /api/restsecurity/access_token

      + +Description: +

      Obtains a bearer access token that can be used to authenticate the subject making this request during +future requests. All bearer access tokens for this subject that have been obtained earlier are invalidated +by this request. The subject can be authenticated by means of +a session ID given as the value for the JSESSIONID cookie. Alternatively, the subject can be authenticated using +the username and password POST parameters. Subject authentication may also +work by providing a bearer access token as obtained by an earlier call to this +service end point. The bearer token has to be provided as an HTTP request header field "Authorization: Bearer <bearer-token>" +Lastly, basic authentication can be used where technically the username and password are provided as base64-encoded string in +the "Authorization:" header field like this: "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==". Most REST clients use +shorthand URL notations with ":" and "@" to denote basic authentication, with these URL elements not being logged.

      +
      + + + + + + + + + + + + + + + + + +
      Request Method:GET/POST
      Output format:JSON document of the following format
      +
      {"username":"admin","access_token":"tnN1yVnXRuqEce3JqMpLUl7d+qeofez0tljKU1KzALw="}
      +
      Mandatory parameters:None
      Examples: +
      Using the session ID in a session cookie:
      + curl -D /tmp/header -b JSESSIONID=5e94c93e-b515-4bcd-8df8-0c786805a9e0 http://127.0.0.1:8888/security/api/restsecurity/access_token +
      +
      Using POST username and password form parameters:
      + curl -d "username=admin&password=admin"-D /tmp/header http://127.0.0.1:8888/security/api/restsecurity/access_token +
      +
      Using a bearer access token:
      + curl -D /tmp/header -H "Authorization: Bearer MVPg/uCV1QGqMDmDtUuOQBrSrB1NlIldm0LW+dnxZ2A=" http://127.0.0.1:8888/security/api/restsecurity/access_token +
      +
      Using basic authentication:
      + curl -D /tmp/header http://admin:admin@127.0.0.1:8888/security/api/restsecurity/access_token +
      +
      + + diff --git a/java/com.sap.sse.security/webservices/api/hello.html b/java/com.sap.sse.security/webservices/api/hello.html new file mode 100755 index 00000000000..f8b5d9d77c3 --- /dev/null +++ b/java/com.sap.sse.security/webservices/api/hello.html @@ -0,0 +1,58 @@ + + + + + + + + SAP Sponsorships Engine Security Webservices API + + +

      SAP Sponsorships Engine Security Webservices API

      +

      URL: /api/restsecurity/hello

      + +Description: +

      Shows the currently authenticated subject and its properties. The subject can be authenticated by means of +a session ID given as the value for the JSESSIONID cookie. Alternatively, the subject can be authenticated using +the username and password POST parameters. Subject authentication may also +work by providing a bearer access token as obtained by the access_token +service end point. The bearer token has to be provided as an HTTP request header field "Authorization: Bearer <bearer-token>" +Lastly, basic authentication can be used where technically the username and password are provided as base64-encoded string in +the "Authorization:" header field like this: "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==". Most REST clients use +shorthand URL notations with ":" and "@" to denote basic authentication, with these URL elements not being logged.

      +
      + + + + + + + + + + + + + + + + + +
      Request Method:GET/POST
      Output format:JSON document of the following format
      +
      {"principal":"admin","authenticated":true,"remembered":false}
      +
      Mandatory parameters:None
      Examples: +
      Using the session ID in a session cookie:
      + curl -D /tmp/header -b JSESSIONID=5e94c93e-b515-4bcd-8df8-0c786805a9e0 http://127.0.0.1:8888/security/api/restsecurity/hello +
      +
      Using POST username and password form parameters:
      + curl -d "username=admin&password=admin"-D /tmp/header http://127.0.0.1:8888/security/api/restsecurity/hello +
      +
      Using a bearer access token:
      + curl -D /tmp/header -H "Authorization: Bearer MVPg/uCV1QGqMDmDtUuOQBrSrB1NlIldm0LW+dnxZ2A=" http://127.0.0.1:8888/security/api/restsecurity/hello +
      +
      Using basic authentication:
      + curl -D /tmp/header http://admin:admin@127.0.0.1:8888/security/api/restsecurity/hello +
      +
      + + diff --git a/java/com.sap.sse.security/webservices/api/index.html b/java/com.sap.sse.security/webservices/api/index.html new file mode 100755 index 00000000000..ed37dfd0078 --- /dev/null +++ b/java/com.sap.sse.security/webservices/api/index.html @@ -0,0 +1,48 @@ + + + + + + + + SAP Sailing Analytics Security Webservices API + + +

      SAP Sport Sponsorships Engine Security Webservices API - Overview

      + +

      +The SAP Sport Sponsorships Engine (SSE) and the applications built with it support security with +various authentication and authorization facilities. This module offers REST services that can be +used to log in and log out a subject, obtain a bearer token for a subject and manage security concerns. +By default, the base URL for these services is "/security", so a full login URL, for example, could +be http://sapsailing.com/security/api/restsecurity/login. +

      +
      + + + + + + + + + + + + + + + + + + + + + + + + + +
      Webservice URLDescription
      /api/restsecurity/loginLog in a subject
      /api/restsecurity/logoutLog out a subject
      /api/restsecurity/helloTest that a subject is authenticated
      /api/restsecurity/access_tokenObtain a bearer access token
      + + \ No newline at end of file diff --git a/java/com.sap.sse.security/webservices/api/login.html b/java/com.sap.sse.security/webservices/api/login.html new file mode 100755 index 00000000000..aceb9509686 --- /dev/null +++ b/java/com.sap.sse.security/webservices/api/login.html @@ -0,0 +1,44 @@ + + + + + + + + SAP Sponsorships Engine Security Webservices API + + +

      SAP Sponsorships Engine Security Webservices API

      +

      URL: /api/restsecurity/login

      + +Description: +

      The service authenticates a subject based on POST form parameter and creates a session. + The session is represented by the JSESSIONID cookie value which can be used in subsequent + requests to the same session with the same authenticated subject.

      +
      + + + + + + + + + + + + + + + + + +
      Request Method:POST
      Output format:Redirect (302 Found) to /api/restsecurity/hello which delivers a JSON document of the following format
      +
      {"principal":"admin","authenticated":true,"remembered":false}
      +
      Mandatory parameters: +
      username
      +
      password
      +
      Example:curl -L -D /tmp/header -d "username=admin&password=admin" http://127.0.0.1:8888/security/api/restsecurity/login
      + Will provide a redirect and a JSON document as explained above.
      + + diff --git a/java/com.sap.sse.security/webservices/api/logout.html b/java/com.sap.sse.security/webservices/api/logout.html new file mode 100755 index 00000000000..6324bef6a13 --- /dev/null +++ b/java/com.sap.sse.security/webservices/api/logout.html @@ -0,0 +1,44 @@ + + + + + + + + SAP Sponsorships Engine Security Webservices API + + +

      SAP Sponsorships Engine Security Webservices API

      +

      URL: /api/restsecurity/logout

      + +Description: +

      The service terminates a session, based on the JSESSIONID cookie. The session's subject is no longer +considered authenticated and the JSESSIONID used to make the request is considered invalid from now on.

      +
      + + + + + + + + + + + + + + + + + +
      Request Method:GET/POST
      Output format:Redirect (302 Found) to / which is not expected to deliver anything +
      Mandatory parameters:None
      Example:curl -D /tmp/header -b JSESSIONID=5e94c93e-b515-4bcd-8df8-0c786805a9e0 http://127.0.0.1:8888/security/api/restsecurity/logout
      + Will return something like
      
      +	            HTTP/1.1 302 Found
      +		    Set-Cookie: rememberMe=deleteMe; Path=/security; Max-Age=0; Expires=Tue, 24-Feb-2015 15:58:16 GMT
      +		    Set-Cookie: JSESSIONID=deleteMe; Path=/; Max-Age=0; Expires=Tue, 24-Feb-2015 15:58:16 GMT
      +		    Location: http://127.0.0.1:8888/security/
      +		    Content-Length: 0
      + + diff --git a/java/com.sap.sse.shared.android/src/com/sap/sse/util/impl/NonGwtUrlHelper.java b/java/com.sap.sse.shared.android/src/com/sap/sse/util/impl/NonGwtUrlHelper.java new file mode 100644 index 00000000000..5a19f9d7fd8 --- /dev/null +++ b/java/com.sap.sse.shared.android/src/com/sap/sse/util/impl/NonGwtUrlHelper.java @@ -0,0 +1,23 @@ +package com.sap.sse.util.impl; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; + +import com.sap.sse.common.util.UrlHelper; + +public enum NonGwtUrlHelper implements UrlHelper { + INSTANCE; + + public String encodeUrl(String decodedUrlString) { + try { + URL url = new URL(decodedUrlString); + URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), + url.getQuery(), url.getRef()); + return uri.toString(); + } catch (MalformedURLException | URISyntaxException f) { + return null; + } + } +} diff --git a/java/com.sap.sse/META-INF/MANIFEST.MF b/java/com.sap.sse/META-INF/MANIFEST.MF index ecb82aed73d..f241ed100a1 100644 --- a/java/com.sap.sse/META-INF/MANIFEST.MF +++ b/java/com.sap.sse/META-INF/MANIFEST.MF @@ -10,12 +10,14 @@ Export-Package: com.sap.sse, com.sap.sse.i18n, com.sap.sse.i18n.impl, com.sap.sse.osgi, + com.sap.sse.qrcode, com.sap.sse.util, com.sap.sse.util.impl, com.sap.sse.util.kmeans Bundle-ActivationPolicy: lazy Require-Bundle: com.sap.sse.common, - com.sap.sse.shared.android + com.sap.sse.shared.android, + org.apache.servicemix.bundles.zxing;bundle-version="3.1.0" Import-Package: javax.servlet;version="3.1.0", javax.servlet.http;version="3.1.0", org.osgi.framework;version="1.8.0", diff --git a/java/com.sap.sse/src/com/sap/sse/i18n/ResourceBundleStringMessages.java b/java/com.sap.sse/src/com/sap/sse/i18n/ResourceBundleStringMessages.java index b752847d119..e3ffc0d0725 100644 --- a/java/com.sap.sse/src/com/sap/sse/i18n/ResourceBundleStringMessages.java +++ b/java/com.sap.sse/src/com/sap/sse/i18n/ResourceBundleStringMessages.java @@ -6,6 +6,16 @@ import java.util.Map; /** * Allow server-side internationalization similar to GWT client-side by using property files. + * + * Get Locale in GWT-Context by calling + *
      + * LocaleInfo.getCurrentLocale().getLocaleName();
      + * 
      + * + * Then transform back to {@link Locale} on server by calling + *
      + * ResourceBundleStringMessages.Util.getLocaleFor(localeInfoName);
      + * 
      */ public interface ResourceBundleStringMessages { @@ -17,21 +27,27 @@ public interface ResourceBundleStringMessages { public static final class Util { private static boolean supportedLocalesHaveBeenInitialized = false; - private static final String DEFAULT_LOCALE_NAME = "default"; private static final Locale DEFAULT_LOCALE = Locale.ENGLISH; private static final Map supportedLocalesMappedByLocaleInfo = new HashMap<>(); public static Locale getLocaleFor(String localeInfoName) { + Locale locale = getSupportedLocalesMap().get(localeInfoName); + return locale != null ? locale : DEFAULT_LOCALE; + } + + public static Iterable getSupportedLocales() { + return getSupportedLocalesMap().values(); + } + + private static Map getSupportedLocalesMap() { if (!supportedLocalesHaveBeenInitialized) { initializeSupportedLocales(); } - Locale locale = Util.supportedLocalesMappedByLocaleInfo.get(localeInfoName); - return locale != null ? locale : DEFAULT_LOCALE; + return supportedLocalesMappedByLocaleInfo; } private static void initializeSupportedLocales() { - supportedLocalesMappedByLocaleInfo.put(DEFAULT_LOCALE_NAME, DEFAULT_LOCALE); supportedLocalesMappedByLocaleInfo.put("en", Locale.ENGLISH); supportedLocalesMappedByLocaleInfo.put("de", Locale.GERMAN); diff --git a/java/com.sap.sse/src/com/sap/sse/qrcode/QRCodeGenerationUtil.java b/java/com.sap.sse/src/com/sap/sse/qrcode/QRCodeGenerationUtil.java new file mode 100644 index 00000000000..b4353f7139a --- /dev/null +++ b/java/com.sap.sse/src/com/sap/sse/qrcode/QRCodeGenerationUtil.java @@ -0,0 +1,66 @@ +package com.sap.sse.qrcode; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Hashtable; + +import javax.imageio.ImageIO; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.QRCodeWriter; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; + +/** + * QRCode generator based on the ZXing library. + * + * TODO have a look at repackaging QRGen as OSGi bundle. + * + * @author Fredrik Teschke + * + */ +public class QRCodeGenerationUtil { + public static InputStream create(String text, int sizeInPixels) throws WriterException, IOException { + Hashtable hintMap = new Hashtable(); + hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L); + return create(text, sizeInPixels, hintMap); + } + + public static InputStream create(String text, int sizeInPixels, + Hashtable hintMap) throws WriterException, IOException { + //taken from http://crunchify.com/java-simple-qr-code-generator-example/ + + QRCodeWriter qrCodeWriter = new QRCodeWriter(); + BitMatrix byteMatrix = qrCodeWriter.encode(text, BarcodeFormat.QR_CODE, sizeInPixels, sizeInPixels, hintMap); + int matrixSize = byteMatrix.getWidth(); + BufferedImage image = new BufferedImage(matrixSize, matrixSize, BufferedImage.TYPE_INT_RGB); + image.createGraphics(); + + Graphics2D graphics = (Graphics2D) image.getGraphics(); + graphics.setColor(Color.WHITE); + graphics.fillRect(0, 0, matrixSize, matrixSize); + graphics.setColor(Color.BLACK); + + for (int i = 0; i < matrixSize; i++) { + for (int j = 0; j < matrixSize; j++) { + if (byteMatrix.get(i, j)) { + graphics.fillRect(i, j, 1, 1); + } + } + } + + //TODO create InputStream from streamed output of image instead of handling complete byte arrays + ByteArrayOutputStream os = new ByteArrayOutputStream(); + ImageIO.write(image, "gif", os); + InputStream is = new ByteArrayInputStream(os.toByteArray()); + return is; + } +} diff --git a/java/com.sap.sse/src/com/sap/sse/util/ServiceTrackerFactory.java b/java/com.sap.sse/src/com/sap/sse/util/ServiceTrackerFactory.java new file mode 100644 index 00000000000..3af71035c02 --- /dev/null +++ b/java/com.sap.sse/src/com/sap/sse/util/ServiceTrackerFactory.java @@ -0,0 +1,18 @@ +package com.sap.sse.util; + +import org.osgi.framework.BundleContext; +import org.osgi.util.tracker.ServiceTracker; + +public class ServiceTrackerFactory { + /** + * @return {@code null} if {@code context} is {@code null} + */ + public static ServiceTracker createAndOpen(BundleContext context, Class clazz) { + if (context == null) { + return null; + } + ServiceTracker result = new ServiceTracker(context, clazz, null); + result.open(); + return result; + } +} diff --git a/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF b/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF index b2f04cadba2..900b4166852 100644 --- a/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF +++ b/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: TracTrac Client module Bundle-SymbolicName: com.tractrac.clientmodule -Bundle-Version: 3.0.7 +Bundle-Version: 3.0.10 Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Bundle-ClassPath: ., lib/TracAPI.jar diff --git a/java/com.tractrac.clientmodule/README.txt b/java/com.tractrac.clientmodule/README.txt index 31ed42bd96f..a8c6776fdc9 100644 --- a/java/com.tractrac.clientmodule/README.txt +++ b/java/com.tractrac.clientmodule/README.txt @@ -16,6 +16,57 @@ It contains also some files: - test.sh -> script that compiles the code in the src folder, creates the test.jar library and execute the code of the example. - Manifest.txt -> manifest used to create the test.jar file +******************************************** +************* TracAPI 3.0.10 *************** +******************************************** +This is a final version. Only fixes bugs in the implementation + +Release date: 17/02/2015 +Build number: 9938 + + 1) Bugs + + - It fixes the same bug described in the release 3.0.9 that it was not fixed. The release 3.0.9 fixed + the synchronization issue between different threads but the ConcurrentModificationException happens + when the same thread is accessing to the list. The synchronization at method level doesn't work + in this case (Reported by Axel Uhl,07/02/2015) + +******************************************** +************* TracAPI 3.0.9 **************** +******************************************** +This is a final version. Only fixes bugs in the implementation + +Release date: 13/02/2015 +Build number: 9904 + + 1) Bugs + + - There is a ConcurrentModificationException in the IConnectionStatusListener subscription, when a thread + is adding subscriptions/unsubscriptions and other thread is sending events through this listener (Reported + by Axel Uhl,07/02/2015) + +******************************************** +************* TracAPI 3.0.8 **************** +******************************************** +This is a final version. It fixes bugs in the implementation and it adds a some features. +These features add methods to the API, but they keep the backward compatibility. +This version provides a new JavaDoc version. + +Release date: 07/02/2015 +Build number: 9826 + + 1) New features + + - Adding the method IEvent.getEventType that returns the type of the event (e.g: Sailing, Orienteering...). + (Requested by Jorge Piera, 01/02/2014) + + 2) Bugs + + - The changes in the static fields of the route (including the metadata) were not propagated through + the subscription library. Following the same approach used for the races, controls and competitors, + a new listener IRoutesListener has been added to manage these changes. (Reported by Axel Uhl, + 06/02/2015) + ******************************************** ************* TracAPI 3.0.7 **************** ******************************************** diff --git a/java/com.tractrac.clientmodule/lib/TracAPI.jar b/java/com.tractrac.clientmodule/lib/TracAPI.jar index 57e8e32f66f..7a33f5cd105 100644 Binary files a/java/com.tractrac.clientmodule/lib/TracAPI.jar and b/java/com.tractrac.clientmodule/lib/TracAPI.jar differ diff --git a/java/com.tractrac.clientmodule/pom.xml b/java/com.tractrac.clientmodule/pom.xml index 93c4be791b1..16743da1b7e 100755 --- a/java/com.tractrac.clientmodule/pom.xml +++ b/java/com.tractrac.clientmodule/pom.xml @@ -8,6 +8,6 @@ 1.0.0-SNAPSHOT com.tractrac.clientmodule - 3.0.7 + 3.0.10 eclipse-plugin diff --git a/java/org.moxieapps.gwt.highcharts/src/org/moxieapps/gwt/highcharts/client/BaseChart.java b/java/org.moxieapps.gwt.highcharts/src/org/moxieapps/gwt/highcharts/client/BaseChart.java index 74201014580..cbffc78b2f6 100755 --- a/java/org.moxieapps.gwt.highcharts/src/org/moxieapps/gwt/highcharts/client/BaseChart.java +++ b/java/org.moxieapps.gwt.highcharts/src/org/moxieapps/gwt/highcharts/client/BaseChart.java @@ -2198,6 +2198,7 @@ public abstract class BaseChart extends Widget { public T redraw() { if (isRendered()) { nativeRedraw(chart); + nativeAdjustCheckboxPosition(chart); } return returnThis(); } @@ -2903,6 +2904,24 @@ public abstract class BaseChart extends Widget { return seriesList.get(seriesIndex).getPlotOptions().getDataLabels().getFormatter().format(new DataLabelsData(nativeData)); } + /** + * Fixing a bug currently in the base Highcharts implementation regarding the placement of the checkbox in the legend. + * Requires the symbol padding (see {@link Legend#setSymbolPadding(Number)}) to be set to 25. Invoked + * whenever the chart is rendered.

      + * + * The implementation moves the checkbox between the legend symbol and the legend item, using the space made by setting the symbol + * padding to 25px. + */ + private static native JavaScriptObject nativeAdjustCheckboxPosition(JavaScriptObject chart) /*-{ + var series = chart.series; + for(var i= 0; icom.sap.sse com.sap.sse.common com.sap.sse.shared.android + com.sap.sse.mail + com.sap.sse.mail.replication.test + com.sap.sse.mail.replication.testsupport com.sap.sse.mongodb com.sap.sse.gwt com.sap.sse.gwt.adminconsole @@ -28,6 +31,7 @@ com.sap.sse.operationaltransformation com.sap.sse.replication com.sap.sse.filestorage + com.sap.sse.gwt.theme com.sap.sailing.domain.common com.sap.sailing.domain.shared.android com.sap.sailing.domain @@ -56,6 +60,7 @@ com.sap.sailing.www com.sap.sailing.www.events com.sap.sailing.xmlexport + com.google.gwt.dev com.google.gwt.osgi com.tractrac.clientmodule org.json.simple @@ -81,9 +86,9 @@ com.sap.sailing.gwt.ui com.sap.sailing.domain.test com.sap.sse.replication.testsupport - com.sap.sse.gwt.test - --> + com.sap.sse.datamining.test com.sap.sse.security.replication.test com.sap.sse.security.test @@ -149,13 +154,13 @@ -Dfile.encoding=cp1252 -Dmongo.dbName=winddbTest -DspatialWind=true - -ea -Xmx1024m -XX:PermSize=256m -XX:+UseG1GC + -ea -Xmx1024m -XX:+UseG1GC -Dtractrac.tunnel=true -Dtractrac.tunnel.host=localhost - -Dmongo.host=localhost + -Dmongo.host=127.0.0.1 -Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=y @@ -184,7 +189,7 @@ no-debug.without-proxy - ${parameters.common} ${parameters.vm-settings} + ${parameters.common} ${parameters.vm-settings} ${parameters.mongodb} @@ -192,7 +197,7 @@ debug.with-proxy - ${parameters.common} ${parameters.vm-settings} ${parameters.proxy} ${parameters.debug} + ${parameters.common} ${parameters.vm-settings} ${parameters.proxy} ${parameters.debug} ${parameters.mongodb} @@ -200,7 +205,7 @@ debug.without-proxy - ${parameters.common} ${parameters.vm-settings} ${parameters.debug} + ${parameters.common} ${parameters.vm-settings} ${parameters.debug} ${parameters.mongodb} @@ -256,6 +261,15 @@ ${tycho-version} p2 + + + + eclipse-plugin + com.google.gwt.dev + 0.0.0 + + + ${project.groupId} diff --git a/java/target/compareServers b/java/target/compareServers new file mode 100755 index 00000000000..1df56a83ed2 --- /dev/null +++ b/java/target/compareServers @@ -0,0 +1,51 @@ +#!/bin/sh + +# Given the base URLs of two SAP Sailing servers, obtains their /sailingserver/api/v1/leaderboardgroups +# service output, sorts it, compares them, and if equal, obtains all corresponding leaderboardgroups/* +# documents and compares those in turn. + +options='c' +while getopts $options option +do + case $option in + c) continue="1";; + \?) echo "Invalid option" + exit 4;; + esac +done +shift $((OPTIND-1)) + +OLDSERVER=$1 +NEWSERVER=$2 +OLDGROUPS=leaderboardgroups.old.sed +NEWGROUPS=leaderboardgroups.new.sed + +# If we continue, don't download again +if [ "$continue" != "1" ]; then + wget -O - ${OLDSERVER}/sailingserver/api/v1/leaderboardgroups | sed -e 's/,/\n/g' -e 's/\[/\n/' -e 's/\]/\n/' -e 's/\"//g' | grep -v "^$" | sort >$OLDGROUPS + wget -O - ${NEWSERVER}/sailingserver/api/v1/leaderboardgroups | sed -e 's/,/\n/g' -e 's/\[/\n/' -e 's/\]/\n/' -e 's/\"//g' | grep -v "^$" | sort >$NEWGROUPS +fi + +diff leaderboardgroups.old.sed leaderboardgroups.new.sed +if [ "$?" = "0" ]; then + OLD_LB=`head -n 1 $OLDGROUPS` + NEW_LB=`head -n 1 $NEWGROUPS` + while [ "$OLD_LB" != "" ]; do + wget -O - "${OLDSERVER}/sailingserver/api/v1/leaderboardgroups/`echo $OLD_LB | sed -e 's/ /%20/g'`" | sed -e 's/\b470W\b/470/g' -e 's/\b470M\b/470/g' -e 's/\<505\>/5O5/g' -e 's/Laser Int\./Laser/g' | python -m json.tool | grep -v timepoint >"${OLD_LB}.old.json" + wget -O - "${NEWSERVER}/sailingserver/api/v1/leaderboardgroups/`echo $NEW_LB | sed -e 's/ /%20/g'`" | sed -e 's/\b470W\b/470/g' -e 's/\b470M\b/470/g' -e 's/\<505\>/5O5/g' -e 's/Laser Int\./Laser/g' | python -m json.tool | grep -v timepoint >"${NEW_LB}.new.json" + diff -b -i -B "${OLD_LB}.old.json" "${NEW_LB}.new.json" + DIFFRESULT=$? + if [ "$DIFFRESULT" != "0" ]; then + echo "Difference detected in leaderboard group $OLD_LB vs. $NEW_LB; exiting" + exit $DIFFRESULT + else + # no diff; remove the leaderboards successfully compared from both leaderboardgroups lists in case a later -c invocation occurs + sed -i -e '1d' $OLDGROUPS + sed -i -e '1d' $NEWGROUPS + fi + OLD_LB=`head -n 1 $OLDGROUPS` + NEW_LB=`head -n 1 $NEWGROUPS` + done +fi + + diff --git a/java/target/configuration/security.properties b/java/target/configuration/mail.properties similarity index 100% rename from java/target/configuration/security.properties rename to java/target/configuration/mail.properties diff --git a/java/target/env.sh b/java/target/env.sh index 8ef97faa381..80556db3319 100644 --- a/java/target/env.sh +++ b/java/target/env.sh @@ -41,7 +41,7 @@ EXPEDITION_PORT=2010 # set the REPLICATE_MASTER_EXCHANGE_NAME variable to the # same channel the master is using in its REPLICATION_CHANNEL variable -# REPLICATE_ON_START=com.sap.sailing.server.impl.RacingEventServiceImpl,com.sap.sse.security.impl.SecurityServiceImpl,com.sap.sse.filestorage.impl.FileStorageManagementServiceImpl +# REPLICATE_ON_START=com.sap.sailing.server.impl.RacingEventServiceImpl,com.sap.sse.security.impl.SecurityServiceImpl,com.sap.sse.filestorage.impl.FileStorageManagementServiceImpl,com.sap.sse.mail.impl.MailServiceImpl # Host where the master Java instance is running # Make sure firewall configurations allow access diff --git a/mobile/com.sap.sailing.racecommittee.app/lint.xml b/mobile/com.sap.sailing.racecommittee.app/lint.xml index 12d0844263e..5320a5bc7ac 100644 --- a/mobile/com.sap.sailing.racecommittee.app/lint.xml +++ b/mobile/com.sap.sailing.racecommittee.app/lint.xml @@ -1,4 +1,5 @@ + diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/OfflineDataManager.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/OfflineDataManager.java index 17a2e2fbb73..90c34fcf353 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/OfflineDataManager.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/OfflineDataManager.java @@ -90,11 +90,11 @@ public class OfflineDataManager extends DataManager { medal), new EmptyRegattaConfiguration()); List competitors = new ArrayList(); - competitors.add(new CompetitorImpl(UUID.randomUUID(), "SAP Extreme Sailing Team", Color.BLUE, null, null)); - competitors.add(new CompetitorImpl(UUID.randomUUID(), "The Wave Muscat", Color.LIGHT_GRAY, null, null)); - competitors.add(new CompetitorImpl(UUID.randomUUID(), "Red Bull Extreme Sailing Team", Color.RED, null, null)); - competitors.add(new CompetitorImpl(UUID.randomUUID(), "Team Korea", Color.GREEN, null, null)); - competitors.add(new CompetitorImpl(UUID.randomUUID(), "Realteam", Color.BLACK, null, null)); + competitors.add(new CompetitorImpl(UUID.randomUUID(), "SAP Extreme Sailing Team", Color.BLUE, null, null, null)); + competitors.add(new CompetitorImpl(UUID.randomUUID(), "The Wave Muscat", Color.LIGHT_GRAY, null, null, null)); + competitors.add(new CompetitorImpl(UUID.randomUUID(), "Red Bull Extreme Sailing Team", Color.RED, null, null, null)); + competitors.add(new CompetitorImpl(UUID.randomUUID(), "Team Korea", Color.GREEN, null, null, null)); + competitors.add(new CompetitorImpl(UUID.randomUUID(), "Realteam", Color.BLACK, null, null, null)); RaceLogEventFactory factory = new RaceLogEventFactoryImpl(); RaceLog log = new RaceLogImpl(UUID.randomUUID()); diff --git a/wiki/local-gwt.md b/wiki/local-gwt.md index 02e687f4165..965bb6e2217 100644 --- a/wiki/local-gwt.md +++ b/wiki/local-gwt.md @@ -28,4 +28,12 @@ One of the great strengths of GWT is the use of Eclipse as a Java source-level d You can set breakpoints in your GWT Java code and inspect values for suspended threads as usual for any Java development. -Note the reduced performance which is largely due to the way the Java VM and the browser plug-in communicate. In particular, many fine-grained changes to the DOM can be quite costly. Therefore, if you're only interested in server-side debugging, consider [compiling](#Local-GWT-Compile) the GWT code for better performance. \ No newline at end of file +Note the reduced performance which is largely due to the way the Java VM and the browser plug-in communicate. In particular, many fine-grained changes to the DOM can be quite costly. Therefore, if you're only interested in server-side debugging, consider [compiling](#Local-GWT-Compile) the GWT code for better performance. + +## Curious Errors + +There's a randomly occuring error, since the update to GWT 2.7, where an entry point fails to load. The Development Mode view of Eclipse shows the error +

      +Your source appears not to live underneath a subpackage called 'client'; no problem, but you'll need to use the <source> directive in your module to make it accessible
      +
      +The problem can be "solved" by retrying until it works. \ No newline at end of file diff --git a/wiki/security.md b/wiki/security.md new file mode 100644 index 00000000000..09bc53d89bf --- /dev/null +++ b/wiki/security.md @@ -0,0 +1,87 @@ +# Security + +The Sports Sponsorships Engine (SSE) on which the SAP Sailing Analytics and the SAP Tennis Analytics are based, uses Apache Shiro to implement security. This in particular includes authentication and authorization. This document does not aim to replace the Shiro documentation which is available, e.g., at [http://shiro.apache.org/configuration.html](http://shiro.apache.org/configuration.html), [http://shiro.apache.org/permissions.html](http://shiro.apache.org/permissions.html) and [http://shiro.apache.org/web.html](http://shiro.apache.org/web.html). + +## Users, Sessions, Roles, and Permissions + +Users are identified in an authentication process. This can currently be a username/password login implemented by posting a form, through HTTP basic authentication (an "Authorization: Basic " HTTP header field) or by using an OAuth-like bearer access token that can be obtained by an authenticated user through a RESTful web service. OAuth-based authentication by external OAuth providers and SAP ID is planned (see [Bug 2482](http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=2482)). + +The response for an HTTP request by an authenticated user contains the JSESSIONID cookie whose value is the session key. Our sessions have a default timeout of 30 minutes which is provided as the Shiro default (see `org.apache.shiro.session.mgt.DefaultSessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT` and its use in the `org.apache.shiro.session.mgt.SimpleSession` constructor). Each request performed with this session key will renew the session timeout. + +Logging out, e.g., by invoking the `/security/api/restsecurity/logout` service, invalidates the session key, and a new authentication will be required to obtain a new session. + +An authenticated user has zero or more roles and zero or more immediate permissions assigned. Roles, in turn, can imply more permissions which are added to the immediate permissions to result in the complete set of permissions the user has. Roles and immediate permissions are stored persistently in an instance of `com.sap.sse.security.UserStore` and can be dynamically adjusted during application run-time. + +The inference from roles to the permissions implied by those roles happens by implementations of the `com.sap.sse.security.shared.PermissionsForRoleProvider` interface. Authentication is mainly performed by so-called "Realm" implementations. SSE provides an abstract base class `com.sap.sse.security.AbstractUserStoreBasedRealm`. It supports setting a `PermissionsForRoleProvider`. Additionally, a `PermissionsForRoleProvider` can be used with `com.sap.sse.security.ui.shared.UserDTO.getAllPermissions(...)` so that UI clients can have the same set of permissions inferred for a user that the Shiro back-end will use for permission checks, so as to enable an disable UI features based on the user's permissions. + +## How to Configure + +Shiro security is largely configured by `shiro.ini` files in OSGi Web Bundlesand their `WEB-INF/web.xml` descriptors. Shiro web security hinges on the use of servlet filters that are configured in `web.xml`. The corresponding section to enable Shiro security for a Web Bundle looks like this: + + + shiroEnvironmentClass + org.apache.shiro.web.env.IniWebEnvironment + + + org.apache.shiro.web.env.EnvironmentLoaderListener + + + ShiroFilter + org.apache.shiro.web.servlet.ShiroFilter + + + + ShiroFilter + /* + REQUEST + FORWARD + INCLUDE + ERROR + + +For this to work, the Web Bundle requires at least the two bundles `org.apache.shiro.core` and `org.apache.shiro.web` which are provided by the target platform. Furthermore, the bundle should require `com.sap.sse.security` so as to get support for the common user store, session replication support and the common roles and permissions management. + +The Web Bundle then provides a `shiro.ini` file in its classpath root, e.g., directly within its `src` or `resources` source folder. The `shiro.ini` file contains essential configuration information about which realms, which session and which cache manager to use. It also configures URLs for login pages, default success pages and permissions required for access to URLs. The file `com.sap.sse.security/resources/shiro.ini` serves as a reasonable copy template. In the `[urls]` section the `shiro.ini` flie provides so-called filter chains for specific or pattern-based sets of URLs. In particular, the configuration can require the authenticated user to have specific roles and / or specific permissions to access the URL. Note the use of the `AnyOfRolesFilter` and how it is different from the regular `roles` filter. + +All authentication filters inheriting from `com.sap.sse.security.AbstractUserStoreBasedRealm` can provide an instance for the `permissionsForRoleProvider` property in the `shiro.ini` configuration file, e.g., as follows: + + permissionsForRoleProvider = com.sap.sailing.gwt.ui.client.shared.security.SailingPermissionsForRoleProvider + upRealm = com.sap.sse.security.UsernamePasswordRealm + upRealm.credentialsMatcher = $credentialsMatcher + upRealm.permissionsForRoleProvider = $permissionsForRoleProvider + +## How to Implement Permission Checks + +There are generally two ways in which some feature can require the user to be equipped with permissions: declaratively in the `shiro.ini` file's `[urls]` section; or programmatically by using something like ``org.apache.shiro.SecurityUtils.getSubject().checkPermission(...)`` which will throw an `AuthorizationException` in case the user lacks the necessary permissions. + +Example for a declarative permission check: + [urls] + /api/v1/events = bearerToken, perms["event:view"] +This requires users trying to access the URL `/api/v1/events` to be authenticated using a valid `JSESSIONID` cookie or any authentication supported by the `bearerToken` filter such that the authenticated user has permissions that imply the `event:view:*` permission. + +Example for a programmatic check: + SecurityUtils.getSubject().checkPermission("event:view"); + +### Special Case: Permission Checks in the AdminConsole + +The `AdminConsolePanel` provides some generic support for permission handling, aiming at customizing the UI to the user's actual permissions. Ideally, a user would only see UI features he/she has the permission to use. In particular, we don't want to bother the user by showing panels he/she isn't permitted to use at all. + +When an administration entry point uses the `AdminConsolePanel`, each widget shown in the panel can be configured to require any of a set of permissions. For example, a panel for managing users would ask for permission `manage_users`. This is actually short for `manage_users:*:*`, a wildcard permission that implies all more specific permissions such as `manage_users:view:*` or `manage_users:edit:peter`. However, a specific permissions such as `manage_users:edit:peter` doesn't imply `manage_users:*:*` by the rules of wildcard permissions. + +The widget shall be shown as soon as the user has any permission for managing users, even only a specific one. It is then up to the implementation of the widget to further constrain user interaction and information displayed, based on the user's actual permissions. To implement this, the permission check for the appearance of administration console widgets is slightly modified: a widget will appear if the permission it requires implies any of the permissions the user has, or if any of the user's permissions implies the permission required by the widget. This way, a user having a specific permission such as `manage_users:edit:peter` will see a widget that requires `manage_users`. Also, a user having the administrator permission `*` will see the same widget because `*` implies all other permissions, particularly `manage_users`. + +## Standard REST Security Services + +There are a number of RESTlets registered under the `/security` context root that allow RESTful clients to log in and log out a user, as well as obtain a bearer access token for a user which can then be used in conjunction with the `bearerToken` / `BearerTokenOrBasicOrFormAuthenticationFilter` authentication filter. These services are described in more detail at [/security/webservices/api/index.html](http://sapsailing.com/security/webservices/api/index.html). + +## Notes on Replication + +The `SecurityService` implementation is a `Replicable` that is replicated from a master to its replicas and in case of replica-initiated operations also the other way. Also, the `SecurityService` is registered with the OSGi service registry and can be discovered by other components. It has a `UserStore` and a cache manager (`com.sap.sse.security.SessionCacheManager`) that is replication aware. This cache manager has to be configured in the `shiro.ini` file. + +Whenever a cache entry is updated (particularly the details of a user session such as creating a new session, expiring a session or touching a session for timeout refresh), the effect is replicated. For performance reasons, a special rule is in place for the `touch` operation (see `SecurityWebSessionManager.touch(...)`). Instead of replicating this effect immediately, a timer is launched which considers the session timeout and the assumed time it takes to replicate an operation and collects and delays such touch operations and sends them at the latest possible time point to ensure a session that got touched doesn't expire anywhere. + +All operations affecting the `UserStore` are also replicated by the `SecurityService`, in particular the creation, update and removal of users, their roles and their permissions. + +It is planned to enable replicating the `SecurityService` from a different master server than the one used for replicating the application domain data. See also [Bug 2465](http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=2465). This would allow an administrator to set up a separate user management server that works as a central "directory" for several other archive and event servers, sharing user management data across such a landscape. \ No newline at end of file diff --git a/wiki/tracking-app/api-v1.md b/wiki/tracking-app/api-v1.md index 25a07363869..ed7f776f8ad 100644 --- a/wiki/tracking-app/api-v1.md +++ b/wiki/tracking-app/api-v1.md @@ -53,8 +53,8 @@ This information is represented in a URL with the following structure: ``` http:///tracking/checkin ?event_id= - &leaderboard_name= - &competitor_id= + &leaderboardName= + &competitorId= ``` **Additional Notes:** @@ -268,15 +268,22 @@ $ curl -H "Content-Type:image/jpeg" --data-binary @ \ "teamImageUri" : "http://training.sapsailing.com/team_images/9871d3a2c554b27151cacf1422eec048.jpeg" } ``` +## Leaderboard Integration + +The leaderboard buttons loads the online leaderboard into an web-view. + +``` +http:///gwt/Home.html#RegattaPlace:eventId=&navigationTab=Leaderboard&leaderboardName=&showRaceDetails=false&showSettings=false +``` # Buoy Tender (Tonnenleger) App
      -An app aimed at buoy tenders. In v1, this only allows marks of an existing course to be _pinged_. The steps for doing so are +An app aimed at buoy tenders. In v1, this only allows marks of a race to be _pinged_. The steps for doing so are 1. provide the information for which leaderboard and race to ping marks -2. get the course information for that race (potentially form the RaceLog, if no RaceDefinition is yet present) -3. ping the marks in that course by submitting fixes +2. get the marks for that race (potentially form the RaceLog, if no TrackedRace is yet present) +3. ping the marks by submitting fixes ## Check-In Information
      @@ -294,57 +301,40 @@ http:///buoy-tender/checkin _also see [Tracking App Check-In Information](#tracking-checkin-info) for additional notes that might apply_ -## Course Information -
      +## Get Marks _see [bug 2651](http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=2651)_ -**Path:** ``leaderboards/{leaderboard-name}/races/{race-name}/course`` +Do not use the course to get the list of marks to ping. Instead, use the ``RaceLogDefineMarkEvent``s in the RaceLog, or ``TrackedRace#getMarks()``. As the JavaDoc of the latter states, not all marks for a race are necessarily present in the course - e.g. if they are backup buoys to be used in the case of a wind shift. + +**Path:** ``leaderboards/{leaderboard-name}/marks?raceColumn={race-column-name}&fleet={fleet-name}`` **Verb:** ``GET`` -**Response:** ([example link](http://www.sapsailing.com/sailingserver/api/v1/regattas/505%20Worlds%202014/races/SAP%20505%20Worlds%20R1/course)) +**Response:** ``` -{ - "name": "SAP 505 Worlds R1", - "waypoints": [ - { - "name": "Start", - "passingInstruction": "None", - "controlPoint": { - "@class": "ControlPointWithTwoMarks", - "name": "Start", - "left": { - "@class": "Mark", - "name": "Start (1)", - "id": "Start (1)", - "type": "BUOY" - }, - "right": { - "@class": "Mark", - "name": "Start (2)", - "id": "Start (2)", - "type": "BUOY" - } - } - }, - { - "name": "Windward", - "passingInstruction": "None", - "controlPoint": { - "@class": "Mark", - "name": "Windward", - "id": "22a53380-046e-0132-0da7-60a44ce903c3", - "type": "BUOY" - } - }, - ... - ] -} +[ + { + "@class": "Mark", + "name": "Start (1)", + "id": "Start (1)", + "type": "BUOY" + }, + { + "@class": "Mark", + "name": "Start (2)", + "id": "Start (2)", + "type": "BUOY" + }, + { + "@class": "Mark", + "name": "Windward", + "id": "22a53380-046e-0132-0da7-60a44ce903c3", + "type": "BUOY" + } +] ``` -_hint: we cannot use [GET regatta/{reg}/races/{race}/course](http://www.sapsailing.com/sailingserver/webservices/api/v1/raceCourseGetDoc.html), as we operate in a leaderboard context (not necessarily regatta), and if case no RaceDefinition exists, this endpoint currently returns no information_ - ## Ping Marks _see [bug 2652](http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=2652)_