diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..c425e6f --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +mvnw.cmd eol=crlf diff --git a/.gitignore b/.gitignore index 97d0478..e804f0f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ target .project log .DS_Store -coverage-test.xml \ No newline at end of file +coverage-test.xml +.idea/ +ais-coverage.iml diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..c6feb8b Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..6637ced --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..9bcf999 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: java +jdk: + - oraclejdk8 diff --git a/README.md b/README.md index 55aba70..19881d4 100644 --- a/README.md +++ b/README.md @@ -13,19 +13,49 @@ AisCoverage is a tool for calculating how well AIS receivers (sources) cover a g ## Building ## - mvn clean install + ./mvnw clean install ## Developing in Eclipse ## M2 Eclipse plugin or - mvn eclipse:eclipse + ./mvnw eclipse:eclipse + +## Running + + target/ais-coverage-[version]-dist/ais-coverage-[version]/coverage.sh -### Rest API ### +## Rest API ## /coverage/rest/* + +## Storing Coverage Data ## +Only MongoDB is supported at the moment. Mongo v3.4.2 or above needs to be installed and authentication is not yet supported. + +Data (at the highest detailed level) is persisted by a background thread at regular (configurable) intervals. The default is 60 minutes. + +## Handling incoming AIS packets + +To avoid saturating the system with incoming packets, an overflow mechanism is implemented both at the `AisBus` and `CoverageHandler` +levels. The `AisBus` drops packets if lower layers of the application cannot handle more incoming packets. The `CoverageHandler` consumes the +packets provided by the bus through its consumers, but since it requires time to process every single packet, another buffer level is introduced +to let the bus provide as much packets as possible and give handling threads a chance to process messages with dropping as few as possible. + +The size of the `AisBus` can be configured with the `` configuration element, while the `CoverageHandler` can be configured with the `` element: + +```xml + + + 10000 + + + 10000 + +``` + +Past these limits, the system will start overflowing and dropping packets. -### Distribution ### +## Distribution ## A distributable zip file is found [here](http://fuka.dk/snapshots/AisCoverage-0.2.zip).
Be aware: As it contains executable files, your browser may post a warning when you download the file.

@@ -42,7 +72,7 @@ When running tests over longer periods using mongodb you might experience some i The current release makes it possible to see the coverage within a limited timespan, down to a single hour, and with 1 hour intervals.
The data is currently only persisted in memory, so might make an out of memory error, if run over longer amounts of time.
-A sample of how satalite data coverage will be handled is possible, by pressing ctrl, and dragging the mouse over the area of interest. +A sample of how satellite data coverage will be handled is possible, by pressing ctrl, and dragging the mouse over the area of interest.
Examples of configuration files can be found here:
diff --git a/mvnw b/mvnw new file mode 100755 index 0000000..6ecc150 --- /dev/null +++ b/mvnw @@ -0,0 +1,236 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # + # Look for the Apple JDKs first to preserve the existing behaviour, and then look + # for the new JDKs provided by Oracle. + # + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then + # + # Oracle JDKs + # + export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then + # + # Apple JDKs + # + export JAVA_HOME=`/usr/libexec/java_home` + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + local basedir=$(pwd) + local wdir=$(pwd) + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + wdir=$(cd "$wdir/.."; pwd) + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# avoid using MAVEN_CMD_LINE_ARGS below since that would loose parameter escaping in $@ +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..8bb8275 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,146 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +set MAVEN_CMD_LINE_ARGS=%MAVEN_CONFIG% %* + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" + +set WRAPPER_JAR=""%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# avoid using MAVEN_CMD_LINE_ARGS below since that would loose parameter escaping in %* +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml index cf9bba6..faea29d 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ dk.dma.ais ais-coverage - 0.2-SNAPSHOT + 0.6.0-CCG AIS coverage analyzer AIS coverage analyzer @@ -46,17 +46,17 @@ dk.dma.ais.lib ais-lib-communication - 2.2-SNAPSHOT + 2.4-CCG-SNAPSHOT dk.dma.commons dma-commons-app - 0.3-SNAPSHOT + 0.3 dk.dma.enav enav-model - 0.5-SNAPSHOT + 0.5 org.slf4j @@ -66,7 +66,7 @@ org.mongodb mongo-java-driver - 2.11.0 + 3.4.2 org.eclipse.jetty @@ -103,9 +103,47 @@ jersey-json 1.17 + + org.mockito + mockito-core + 2.7.11 + test + + + org.hamcrest + hamcrest-library + 1.3 + test + + + de.bwaldvogel + mongo-java-server + 1.6.0 + test + + + org.apache.commons + commons-lang3 + 3.5 + + + org.apache.commons + commons-collections4 + 4.1 + + + commons-io + commons-io + 2.5 + + + dma-snapshots + Dma Snapshot Repository + http://repository-dma.forge.cloudbees.com/snapshot/ + dma-releases Dma Release Repository diff --git a/src/main/java/dk/dma/ais/coverage/AisCoverage.java b/src/main/java/dk/dma/ais/coverage/AisCoverage.java index f795a78..150be1d 100644 --- a/src/main/java/dk/dma/ais/coverage/AisCoverage.java +++ b/src/main/java/dk/dma/ais/coverage/AisCoverage.java @@ -14,20 +14,33 @@ */ package dk.dma.ais.coverage; -import java.util.function.Consumer; - -import net.jcip.annotations.GuardedBy; -import net.jcip.annotations.ThreadSafe; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import dk.dma.ais.bus.AisBus; import dk.dma.ais.bus.consumer.DistributerConsumer; import dk.dma.ais.coverage.configuration.AisCoverageConfiguration; +import dk.dma.ais.coverage.data.Cell; +import dk.dma.ais.coverage.persistence.DatabaseInstance; +import dk.dma.ais.coverage.persistence.DatabaseInstanceFactory; +import dk.dma.ais.coverage.persistence.PersisterService; +import dk.dma.ais.coverage.persistence.TypeBasedDatabaseInstanceFactory; import dk.dma.ais.coverage.web.WebServer; import dk.dma.ais.packet.AisPacket; import dk.dma.ais.reader.AisReader; +import dk.dma.commons.util.DateTimeUtil; +import net.jcip.annotations.GuardedBy; +import net.jcip.annotations.ThreadSafe; +import org.apache.commons.collections4.IterableUtils; +import org.joda.time.PeriodType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.Duration; +import java.time.Instant; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; /** @@ -46,16 +59,20 @@ public final class AisCoverage { private final AisBus aisBus; private final WebServer webServer; private AisReader aisReader; + private PersisterService persisterService; + private final DatabaseInstance databaseInstance; - private AisCoverage(AisCoverageConfiguration conf) { + private AisCoverage(AisCoverageConfiguration conf, DatabaseInstanceFactory databaseInstanceFactory) { this.conf = conf; - - // Create handler handler = new CoverageHandler(conf); - // Create AisBus + databaseInstance = databaseInstanceFactory.createDatabaseInstance(conf.getDatabaseConfiguration().getType()); + databaseInstance.open(conf.getDatabaseConfiguration()); + databaseInstance.createDatabase(); + loadCoverageDataFromDatabase(); + aisBus = conf.getAisbusConfiguration().getInstance(); - + createPersisterService(); // Create web server if (conf.getServerConfiguration() != null) { @@ -76,29 +93,66 @@ public void accept(AisPacket packet) { aisBus.registerConsumer(unfilteredConsumer); } + private void loadCoverageDataFromDatabase() { + Instant start = Instant.now(); + Map> loadedCoverageData = databaseInstance.loadLatestSavedCoverageData(); + Instant databaseEnd = Instant.now(); + LOG.info("Loading coverage data from database took [{}] ms", Duration.between(start, databaseEnd).toMillis()); + + for (String sourceId : loadedCoverageData.keySet()) { + for (Cell cell : loadedCoverageData.get(sourceId)) { + handler.getDataHandler().updateCell(sourceId, cell); + + adjustSystemEarliestMessageFromCell(cell); + } + } + Instant end = Instant.now(); + LOG.info("Loading coverage data and converting to memory structure took [{}] ms", Duration.between(start, end).toMillis()); + if (Helper.firstMessage != null) { + LOG.info("First message timestamp: {}. Window size should be: {}", Helper.firstMessage, DateTimeUtil.toInterval(Helper.firstMessage, new Date()).toPeriod(PeriodType.hours()).getHours()); + } + } + + private void adjustSystemEarliestMessageFromCell(Cell cell) { + List timeSpanIds = IterableUtils.toList(cell.getFixedWidthSpans().keySet()); + Collections.sort(timeSpanIds); + Long earliestTimeSpanId = timeSpanIds.get(0); + + if (Helper.firstMessage == null) { + Helper.firstMessage = new Date(earliestTimeSpanId.longValue()); + } else if (Helper.firstMessage.getTime() > earliestTimeSpanId) { + Helper.firstMessage = new Date(earliestTimeSpanId); + } + } + + private void createPersisterService() { + persisterService = new PersisterService(databaseInstance, handler.getDataHandler()); + persisterService.intervalInMinutes(conf.getDatabaseConfiguration().getPersistenceIntervalInMinutes()); + } + public void start() { // Start aisBus if (aisReader == null) { aisBus.start(); aisBus.startConsumers(); aisBus.startProviders(); - LOG.info("aisbus startet"); + LOG.info("aisbus started"); } // Start web server if (webServer != null) { try { webServer.start(); - LOG.info("webserver startet"); + LOG.info("webserver started"); } catch (Exception e) { LOG.error("Failed to start web server: " + e.getMessage()); e.printStackTrace(); } } + + persisterService.start(); } public void stop() { - - // Start web server if (webServer != null) { try { webServer.stop(); @@ -109,9 +163,16 @@ public void stop() { } } - // Stop AisBus aisBus.cancel(); + handler.stop(); LOG.info("aisbus stopped"); + + persisterService.stop(); + try { + databaseInstance.close(); + } catch (Exception e) { + LOG.warn("Could not close DatabaseInstance cleanly", e); + } } public AisCoverageConfiguration getConf() { @@ -123,7 +184,12 @@ public CoverageHandler getHandler() { } public static synchronized AisCoverage create(AisCoverageConfiguration conf) { - instance = new AisCoverage(conf); + instance = new AisCoverage(conf, new TypeBasedDatabaseInstanceFactory()); + return instance; + } + + public static synchronized AisCoverage create(AisCoverageConfiguration conf, DatabaseInstanceFactory databaseInstanceFactory) { + instance = new AisCoverage(conf, databaseInstanceFactory); return instance; } @@ -131,4 +197,7 @@ public static synchronized AisCoverage get() { return instance; } + void setPersisterService(PersisterService persisterService) { + this.persisterService = persisterService; + } } diff --git a/src/main/java/dk/dma/ais/coverage/CoverageHandler.java b/src/main/java/dk/dma/ais/coverage/CoverageHandler.java index e1fa5cc..8c319b6 100644 --- a/src/main/java/dk/dma/ais/coverage/CoverageHandler.java +++ b/src/main/java/dk/dma/ais/coverage/CoverageHandler.java @@ -14,13 +14,7 @@ */ package dk.dma.ais.coverage; -import java.util.ArrayList; -import java.util.Date; -import java.util.LinkedHashMap; -import java.util.Map; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +import dk.dma.ais.bus.OverflowLogger; import dk.dma.ais.coverage.calculator.AbstractCalculator; import dk.dma.ais.coverage.calculator.SatCalculator; import dk.dma.ais.coverage.calculator.TerrestrialCalculator; @@ -30,45 +24,55 @@ import dk.dma.ais.coverage.data.OnlyMemoryData; import dk.dma.ais.coverage.data.Ship; import dk.dma.ais.packet.AisPacket; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; /** * Handler for received AisPackets */ public class CoverageHandler { - private static final Logger LOG = LoggerFactory.getLogger(CoverageHandler.class); - - //list of calculators - private ArrayList calculators = new ArrayList(); - public ArrayList getCalculators() { - return calculators; - } + private final OverflowLogger overflowLogger = new OverflowLogger(LOG); - public void setCalculators(ArrayList calculators) { - this.calculators = calculators; - } + private final Queue unhandledPackets = new ConcurrentLinkedQueue<>(); + private final ExecutorService packetHandlingThreadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() / 2); + private final int maximumUnhandledPacketsKept; + + private List calculators = new ArrayList(); private ICoverageData dataHandler; - - public ICoverageData getDataHandler() { - return dataHandler; - } - public void setDataHandler(ICoverageData dataHandler) { - this.dataHandler = dataHandler; - } + private AisCoverageConfiguration conf; //A doublet filtered message buffer, where a custom message will include a list of all sources - private LinkedHashMap doubletBuffer = new LinkedHashMap() { + private Map doubletBuffer = Collections.synchronizedMap(new LinkedHashMap() { private static final long serialVersionUID = 1L; @Override protected boolean removeEldestEntry(Map.Entry eldest) { - process(eldest.getValue()); - return this.size() > 10000; + if (this.size() > getMessageBufferSize()) { + process(eldest.getValue()); + } + return this.size() > getMessageBufferSize(); } - }; - + }); + + private int getMessageBufferSize() { + return this.conf.getMessageBufferSize(); + } //Fields used for debugging purposes private int unfiltCount; private long biggestDelay; @@ -76,69 +80,79 @@ protected boolean removeEldestEntry(Map.Entry eldest) { private int delayedMoreThanTen; private int delayedLessThanTen; - private AisCoverageConfiguration conf; - public CoverageHandler(AisCoverageConfiguration conf) { this.conf=conf; Helper.conf=conf; - + + maximumUnhandledPacketsKept = conf.getReceivedPacketsBufferSize(); + + LOG.info("Using {} thread(s) to handle incoming AIS packets", Runtime.getRuntime().availableProcessors() / 2); + LOG.info("Message buffer size initialized with value [{}]", getMessageBufferSize()); + //Creating up data handler dataHandler = new OnlyMemoryData(); LOG.info("coverage calculators set up with memory only data handling"); - + //creating calculators calculators.add(new TerrestrialCalculator(false)); calculators.add(new SatCalculator()); - + for (AbstractCalculator calc : calculators) { calc.setDataHandler(dataHandler); } // Logging grid granularity - LOG.info("grid granularity initiated with lat: " + conf.getLatSize() + " and lon: " + conf.getLonSize()); - + LOG.info("grid granularity initiated with lat: " + conf.getLatSize() + " and lon: " + conf.getLonSize()); + // One could set grid granularity based on meter scale and a latitude position like this - // Helper.setLatLonSize(meters, latitude); - + // Helper.setLatLonSize(meters, latitude); + if (conf.getVerbosityLevel() > 0) { verboseDebug(); } - + // window size LOG.info("Max window size is " + conf.getWindowSize()+" hours"); Purger purger = new Purger(conf.getWindowSize(), dataHandler, 5); purger.start(); + } + + public List getCalculators() { + return calculators; + } + + public void setCalculators(List calculators) { + this.calculators = calculators; + } + public ICoverageData getDataHandler() { + return dataHandler; + } + + public void setDataHandler(ICoverageData dataHandler) { + this.dataHandler = dataHandler; } public void receiveUnfiltered(AisPacket packet) { unfiltCount++; - - //extract relevant information from packet - CustomMessage message = dataHandler.packetToCustomMessage(packet); - if(message == null){ + + final int numberOfUnhandledPackets = unhandledPackets.size(); + if (numberOfUnhandledPackets >= maximumUnhandledPacketsKept) { + overflowLogger.log("Received AIS packets buffer overflow: " + numberOfUnhandledPackets + " currently unhandled packets"); return; } - - String key = message.getKey(); - - //Add to doublet buffer. - CustomMessage existing = doubletBuffer.get(key); - if (existing == null) { - doubletBuffer.put(key, message); - }else{ - existing.addSourceMMSI(message.getSourceList().iterator().next()); - } + unhandledPackets.add(packet); + + packetHandlingThreadPool.submit(new AisPacketHandler()); } - private void process(CustomMessage m){ + void process(CustomMessage m){ for (AbstractCalculator calc : calculators) { calc.calculate(m); } } - - + public void verboseDebug(){ final Date then = new Date(); Thread t = new Thread(new Runnable() { @@ -190,4 +204,47 @@ public SatCalculator getSatCalc() { return (SatCalculator) calculators.get(1); } + public void stop() { + packetHandlingThreadPool.shutdown(); + + try { + if (!packetHandlingThreadPool.awaitTermination(60, TimeUnit.SECONDS)) { + packetHandlingThreadPool.shutdownNow(); + if (!packetHandlingThreadPool.awaitTermination(60, TimeUnit.SECONDS)) { + LOG.warn("Pool did not terminate"); + } + } + } catch (InterruptedException ie) { + packetHandlingThreadPool.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + + private class AisPacketHandler implements Callable { + + @Override + public Void call() throws Exception { + AisPacket packet = unhandledPackets.poll(); + + //extract relevant information from packet + CustomMessage message = dataHandler.packetToCustomMessage(packet); + if (message == null) { + return null; + } + + String key = message.getKey(); + + //Add to doublet buffer. + CustomMessage existing = doubletBuffer.get(key); + if (existing == null) { + doubletBuffer.put(key, message); + } else { + if (message.getSourceList().iterator().hasNext()) { + existing.addSourceMMSI(message.getSourceList().iterator().next()); + } + } + + return null; + } + } } diff --git a/src/main/java/dk/dma/ais/coverage/Purger.java b/src/main/java/dk/dma/ais/coverage/Purger.java index ab74981..be77200 100644 --- a/src/main/java/dk/dma/ais/coverage/Purger.java +++ b/src/main/java/dk/dma/ais/coverage/Purger.java @@ -14,12 +14,11 @@ */ package dk.dma.ais.coverage; -import java.util.Date; - +import dk.dma.ais.coverage.data.ICoverageData; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import dk.dma.ais.coverage.data.ICoverageData; +import java.util.Date; public class Purger extends Thread { private static final Logger LOG = LoggerFactory.getLogger(Purger.class); @@ -28,8 +27,7 @@ public class Purger extends Thread { private final ICoverageData dataHandler; private final int pollTimeInSeconds; - public Purger(int maxWindowSize, ICoverageData dataHandler, - int pollTimeInSeconds) { + public Purger(int maxWindowSize, ICoverageData dataHandler, int pollTimeInSeconds) { this.maxWindowSize = maxWindowSize; this.dataHandler = dataHandler; this.pollTimeInSeconds = pollTimeInSeconds; @@ -39,20 +37,13 @@ public Purger(int maxWindowSize, ICoverageData dataHandler, public void run() { while (true) { if (Helper.latestMessage != null && Helper.firstMessage != null) { - int windowSize = (int) ((Helper.getCeilDate( - Helper.latestMessage).getTime() - Helper.getFloorDate( - Helper.firstMessage).getTime()) / 1000 / 60 / 60); + int currentWindowSize = getCurrentWindowSize(); - if (windowSize > maxWindowSize) { - Date trimPoint = new Date(Helper.getCeilDate( - Helper.latestMessage).getTime() - - (1000 * 60 * 60 * maxWindowSize)); - LOG.info("Window size: " + windowSize - + ". Max window size: " + maxWindowSize - + ". Lets purge data until " + trimPoint); + if (currentWindowSize > maxWindowSize) { + Date trimPoint = getTrimPoint(); + LOG.info("Window size: {}. Max window size: {}. Lets purge data until {}", currentWindowSize, maxWindowSize, trimPoint); dataHandler.trimWindow(trimPoint); - } } @@ -64,4 +55,14 @@ public void run() { } } + private int getCurrentWindowSize() { + long latestMessageCeilingDate = Helper.getCeilDate(Helper.latestMessage).getTime(); + long firstMessageFloorDate = Helper.getFloorDate(Helper.firstMessage).getTime(); + return (int) ((latestMessageCeilingDate - firstMessageFloorDate) / 1000 / 60 / 60); + } + + Date getTrimPoint() { + return new Date(Helper.getCeilDate(Helper.latestMessage).getTime() - (1000L * 60 * 60 * maxWindowSize)); + } + } diff --git a/src/main/java/dk/dma/ais/coverage/calculator/AbstractCalculator.java b/src/main/java/dk/dma/ais/coverage/calculator/AbstractCalculator.java index a8c6e18..d9b4d06 100644 --- a/src/main/java/dk/dma/ais/coverage/calculator/AbstractCalculator.java +++ b/src/main/java/dk/dma/ais/coverage/calculator/AbstractCalculator.java @@ -14,11 +14,6 @@ */ package dk.dma.ais.coverage.calculator; -import java.io.Serializable; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import dk.dma.ais.coverage.AisCoverageGUI; import dk.dma.ais.coverage.calculator.geotools.SphereProjection; import dk.dma.ais.coverage.data.CustomMessage; @@ -30,6 +25,12 @@ import dk.dma.ais.message.AisMessage5; import dk.dma.ais.message.ShipTypeCargo; import dk.dma.ais.message.ShipTypeCargo.ShipType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Serializable; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * See CoverageCalculator and DensityPlotCalculator for examples of how to extend this class. When a calculator is added to an @@ -137,10 +138,8 @@ public double getExpectedTransmittingFrequency(double sog, boolean rotating, Shi */ public boolean filterMessage(CustomMessage customMessage) { - if (customMessage.getSog() < 3 || customMessage.getSog() > 50) { - return true; - } - if (customMessage.getCog() == 360) { + if (customMessage.getSog() < 0 || customMessage.getSog() > 150) { + LOG.info("filtering message due to SoG: {}, lat: {}, long: {}", customMessage.getSog(), customMessage.getLatitude(), customMessage.getLongitude()); return true; } @@ -155,12 +154,14 @@ public boolean filterMessage(CustomMessage customMessage) { double distance = projection.distBetweenPoints(firstMessage.getLongitude(), firstMessage.getLatitude(), lastMessage.getLongitude(), lastMessage.getLatitude()); if (distance > 2000) { + LOG.info("filtering message due to distance: {}, lat: {}, long: {}", distance, customMessage.getLatitude(), customMessage.getLongitude()); return true; } // Filter message based on time between first and last message double timeDifference = this.getTimeDifference(firstMessage, lastMessage); if (timeDifference > 1200) { + LOG.info("filtering message due to time difference: {}, lat: {}, long: {}", timeDifference, customMessage.getLatitude(), customMessage.getLongitude()); return true; } diff --git a/src/main/java/dk/dma/ais/coverage/calculator/SatCalculator.java b/src/main/java/dk/dma/ais/coverage/calculator/SatCalculator.java index e53160b..3b03afc 100644 --- a/src/main/java/dk/dma/ais/coverage/calculator/SatCalculator.java +++ b/src/main/java/dk/dma/ais/coverage/calculator/SatCalculator.java @@ -397,13 +397,6 @@ private TimeSpan mergeTimeSpans(TimeSpan span1, TimeSpan span2) { */ @Override public boolean filterMessage(CustomMessage customMessage) { - - // if(customMessage.getSog() < 3 || customMessage.getSog() > 50) - // return true; - if (customMessage.getCog() == 360) { - return true; - } - return false; } diff --git a/src/main/java/dk/dma/ais/coverage/calculator/TerrestrialCalculator.java b/src/main/java/dk/dma/ais/coverage/calculator/TerrestrialCalculator.java index e4fe1a4..5d1bd00 100644 --- a/src/main/java/dk/dma/ais/coverage/calculator/TerrestrialCalculator.java +++ b/src/main/java/dk/dma/ais/coverage/calculator/TerrestrialCalculator.java @@ -14,15 +14,6 @@ */ package dk.dma.ais.coverage.calculator; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - import dk.dma.ais.coverage.AisCoverage; import dk.dma.ais.coverage.Helper; import dk.dma.ais.coverage.configuration.AisCoverageConfiguration; @@ -30,20 +21,20 @@ import dk.dma.ais.coverage.data.CustomMessage; import dk.dma.ais.coverage.data.QueryParams; import dk.dma.ais.coverage.data.Ship; -import dk.dma.ais.coverage.data.Ship.ShipClass; -import dk.dma.ais.coverage.data.Source; -import dk.dma.ais.coverage.data.Source.ReceiverType; -import dk.dma.ais.coverage.data.Source_UserProvided; import dk.dma.ais.coverage.event.AisEvent; -import dk.dma.ais.coverage.event.AisEvent.Event; import dk.dma.ais.coverage.event.IAisEventListener; import dk.dma.ais.coverage.export.data.ExportCell; import dk.dma.ais.coverage.export.data.JSonCoverageMap; import dk.dma.ais.coverage.export.data.JsonConverter; -import dk.dma.ais.message.AisMessage; -import dk.dma.ais.message.AisMessage4; -import dk.dma.ais.message.AisPositionMessage; -import dk.dma.ais.proprietary.IProprietarySourceTag; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; /** * This calculator expects a filtered data stream! (No doublets) The stream must not be downsampled! @@ -103,7 +94,11 @@ private boolean checkDoublets(CustomMessage m) { * This is called whenever a message is received */ public void calculate(CustomMessage message) { - + if (message.isVsi()) { + dataHandler.incrementReceivedVsiMessage(AbstractCalculator.SUPERSOURCE_MMSI, message.getLatitude(), message.getLongitude(), message.getTimestamp(), message.getSignalStrength()); + approveMessage(message); + return; + } Ship ship = dataHandler.getShip(message.getShipMMSI()); @@ -205,6 +200,10 @@ private void calculateMissingPoints(CustomMessage m1, CustomMessage m2, boolean Date stamp = new Date((long) (m1.getTimestamp().getTime() + (i * expectedTransmittingFrequency * 1000))); dataHandler.incrementMissingSignals(AbstractCalculator.SUPERSOURCE_MMSI, projection.y2Lat(xMissing, yMissing), projection.x2Lon(xMissing, yMissing), stamp); + for (String source : m1.getSourceList()) { + dataHandler.incrementMissingSignals(source, projection.y2Lat(xMissing, yMissing), + projection.x2Lon(xMissing, yMissing), stamp); + } } } } @@ -212,11 +211,12 @@ private void calculateMissingPoints(CustomMessage m1, CustomMessage m2, boolean private void approveMessage(CustomMessage approvedMessage) { for (String source : approvedMessage.getSourceList()) { - dataHandler.incrementReceivedSignals(source, approvedMessage.getLatitude(), - approvedMessage.getLongitude(), approvedMessage.getTimestamp()); - + if (approvedMessage.isVsi()) { + dataHandler.incrementReceivedVsiMessage(source, approvedMessage.getLatitude(), approvedMessage.getLongitude(), approvedMessage.getTimestamp(), approvedMessage.getSignalStrength()); + } else { + dataHandler.incrementReceivedSignals(source, approvedMessage.getLatitude(), approvedMessage.getLongitude(), approvedMessage.getTimestamp()); + } } -// System.out.println(); } /** @@ -248,7 +248,7 @@ public JSonCoverageMap getTerrestrialCoverage(double latStart, double lonStart, map.latSize = conf.getLatSize() * multiplicationFactor; map.lonSize = conf.getLonSize() * multiplicationFactor; - HashMap JsonCells = new HashMap(); + HashMap JsonCells = new HashMap<>(); QueryParams params = new QueryParams(); params.latStart = latStart; @@ -267,9 +267,7 @@ public JSonCoverageMap getTerrestrialCoverage(double latStart, double lonStart, List celllistSuper = dataHandler.getCells(params); Map superMap = new HashMap(); for (Cell cell : celllistSuper) { - if (cell.getNOofReceivedSignals() > 0) { - superMap.put(cell.getId(), cell); - } + superMap.put(cell.getId(), cell); } if (!celllist.isEmpty()) { @@ -281,12 +279,8 @@ public JSonCoverageMap getTerrestrialCoverage(double latStart, double lonStart, if (superCell == null) { } else { - ExportCell existing = JsonCells.get(cell.getId()); - ExportCell theCell = JsonConverter.toJsonCell(cell, superCell, starttime, endtime); - if (existing == null || theCell.getCoverage() > existing.getCoverage()) { - JsonCells.put(cell.getId(), theCell); - } - + ExportCell theCell = JsonConverter.toJsonCell(cell, superCell, starttime, endtime); + JsonCells.put(cell.getId(), theCell); } } @@ -295,6 +289,11 @@ public JSonCoverageMap getTerrestrialCoverage(double latStart, double lonStart, return map; } + @Override + public boolean filterMessage(CustomMessage customMessage) { + return false; + } + // Getters and setters private double getY(double seconds, Long p1Time, Long p2Time, double p1y, double p2y) { double distanceInMeters = p2y - p1y; diff --git a/src/main/java/dk/dma/ais/coverage/configuration/AisCoverageConfiguration.java b/src/main/java/dk/dma/ais/coverage/configuration/AisCoverageConfiguration.java index 2a7eb70..983170b 100644 --- a/src/main/java/dk/dma/ais/coverage/configuration/AisCoverageConfiguration.java +++ b/src/main/java/dk/dma/ais/coverage/configuration/AisCoverageConfiguration.java @@ -14,12 +14,9 @@ */ package dk.dma.ais.coverage.configuration; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.util.HashMap; -import java.util.Map; +import dk.dma.ais.configuration.bus.AisBusConfiguration; +import dk.dma.ais.coverage.data.Source_UserProvided; +import dk.dma.ais.coverage.web.WebServerConfiguration; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; @@ -27,10 +24,12 @@ import javax.xml.bind.Unmarshaller; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; - -import dk.dma.ais.configuration.bus.AisBusConfiguration; -import dk.dma.ais.coverage.data.Source_UserProvided; -import dk.dma.ais.coverage.web.WebServerConfiguration; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.util.HashMap; +import java.util.Map; /** * Class to represent AIS coverage configuration. To be marshalled and @@ -43,10 +42,12 @@ public class AisCoverageConfiguration { private WebServerConfiguration serverConfiguration; private double latSize = 0.0225225225; private double lonSize = 0.0386812541; + private int messageBufferSize = 10000; private int verbosityLevel; private DatabaseConfiguration dbConf = new DatabaseConfiguration(); private Map sourcenames = new HashMap(); private int windowSize = 5; + private int receivedPacketsBufferSize = 10000; public Map getSourceNameMap() { return sourcenames; @@ -77,6 +78,7 @@ public void setAisbusConfiguration(AisBusConfiguration aisbusConfiguration) { this.aisbusConfiguration = aisbusConfiguration; } + public WebServerConfiguration getServerConfiguration() { return serverConfiguration; } @@ -102,6 +104,14 @@ public double getLonSize() { return this.lonSize; } + public int getMessageBufferSize() { + return messageBufferSize; + } + + public void setMessageBufferSize(int messageBufferSize) { + this.messageBufferSize = messageBufferSize; + } + public static void save(String filename, AisCoverageConfiguration conf) throws JAXBException, FileNotFoundException { JAXBContext context = JAXBContext @@ -137,4 +147,11 @@ public void setWindowSize(int windowSize) { this.windowSize = windowSize; } + public int getReceivedPacketsBufferSize() { + return receivedPacketsBufferSize; + } + + public void setReceivedPacketsBufferSize(int receivedPacketsBufferSize) { + this.receivedPacketsBufferSize = receivedPacketsBufferSize; + } } diff --git a/src/main/java/dk/dma/ais/coverage/configuration/DatabaseConfiguration.java b/src/main/java/dk/dma/ais/coverage/configuration/DatabaseConfiguration.java index 5679544..a57dbae 100644 --- a/src/main/java/dk/dma/ais/coverage/configuration/DatabaseConfiguration.java +++ b/src/main/java/dk/dma/ais/coverage/configuration/DatabaseConfiguration.java @@ -19,6 +19,7 @@ public class DatabaseConfiguration { private String dbName = "nordicCoverage"; private String addr = "localhost"; private int port = 5000; + private int persistenceIntervalInMinutes = 60; public String getType() { return type; @@ -51,4 +52,12 @@ public int getPort() { public void setPort(int port) { this.port = port; } + + public int getPersistenceIntervalInMinutes() { + return persistenceIntervalInMinutes; + } + + public void setPersistenceIntervalInMinutes(int persistenceIntervalInMinutes) { + this.persistenceIntervalInMinutes = persistenceIntervalInMinutes; + } } diff --git a/src/main/java/dk/dma/ais/coverage/data/Cell.java b/src/main/java/dk/dma/ais/coverage/data/Cell.java index 5921c92..a240f2d 100644 --- a/src/main/java/dk/dma/ais/coverage/data/Cell.java +++ b/src/main/java/dk/dma/ais/coverage/data/Cell.java @@ -21,11 +21,13 @@ import java.util.Map; public class Cell { - private int NOofReceivedSignals; private int NOofMissingSignals; + private int numberOfVsiMessages; + private int averageSignalStrength; private double latitude; private double longitude; + private final String id; private List timeSpans; private Map fixedWidthSpans = new HashMap(); @@ -48,26 +50,42 @@ public void setTimeSpans(List timeSpans) { public Cell(Source grid, double lat, double lon, String id) { this.latitude = lat; this.longitude = lon; + this.id = id; } public Cell(double lat, double lon, String id) { this.latitude = lat; this.longitude = lon; + this.id = id; } - public void incrementNOofReceivedSignals() { + public synchronized void incrementNOofReceivedSignals() { NOofReceivedSignals++; } - public void incrementNOofMissingSignals() { + public synchronized void incrementNOofMissingSignals() { NOofMissingSignals++; } - public long getTotalNumberOfMessages() { + public synchronized void incrementNumberOfVsiMessages(int signalStrength) { + int incrementedNumberOfVsiMessages = numberOfVsiMessages + 1; + averageSignalStrength = computeAverageSignalStrength(signalStrength, incrementedNumberOfVsiMessages); + numberOfVsiMessages = incrementedNumberOfVsiMessages; + } + + private int computeAverageSignalStrength(int signalStrength, int incrementedNumberOfVsiMessages) { + if (incrementedNumberOfVsiMessages > 0) { + return Math.floorDiv((numberOfVsiMessages * averageSignalStrength) + signalStrength, incrementedNumberOfVsiMessages); + } else { + return 0; + } + } + + public synchronized long getTotalNumberOfMessages() { return NOofReceivedSignals + NOofMissingSignals; } - public double getCoverage() { + public synchronized double getCoverage() { return (double) NOofReceivedSignals / (double) getTotalNumberOfMessages(); } @@ -88,14 +106,14 @@ public void setLongitude(double longitude) { } public String getId() { - return this.latitude + "_" + this.longitude; + return this.id; } - public int getNOofReceivedSignals(Date starttime, Date endTime) { + public synchronized int getNOofReceivedSignals(Date starttime, Date endTime) { int result = 0; Collection spans = fixedWidthSpans.values(); - for (TimeSpan timeSpan : spans) { + for (TimeSpan timeSpan : spans) { if (timeSpan.getFirstMessage().getTime() >= starttime.getTime() && timeSpan.getLastMessage().getTime() <= endTime.getTime()) { @@ -106,36 +124,87 @@ public int getNOofReceivedSignals(Date starttime, Date endTime) { return result; } - public int getNOofMissingSignals(Date starttime, Date endTime) { + public synchronized int getNOofMissingSignals(Date starttime, Date endTime) { int result = 0; Collection spans = fixedWidthSpans.values(); + for (TimeSpan timeSpan : spans) { if (timeSpan.getFirstMessage().getTime() >= starttime.getTime() && timeSpan.getLastMessage().getTime() <= endTime.getTime()) { result = result + timeSpan.getMissingSignals(); } } + return result; } - public int getNOofReceivedSignals() { + public synchronized int getNOofReceivedSignals() { return this.NOofReceivedSignals; } - public int getNOofMissingSignals() { + public synchronized int getNOofMissingSignals() { return this.NOofMissingSignals; } - public void addReceivedSignals(int amount) { + public synchronized void addReceivedSignals(int amount) { this.NOofReceivedSignals += amount; } - public void addNOofMissingSignals(int amount) { + public synchronized void addNOofMissingSignals(int amount) { this.NOofMissingSignals += amount; } - public void setNoofMissingSignals(int amount) { + public synchronized void setNoofMissingSignals(int amount) { this.NOofMissingSignals = amount; } + public synchronized int getNumberOfVsiMessages() { + return numberOfVsiMessages; + } + + public synchronized int getAverageSignalStrength() { + return averageSignalStrength; + } + + public synchronized int getNumberOfVsiMessages(Date startTime, Date endTime) { + int result = 0; + Collection spans = fixedWidthSpans.values(); + + for (TimeSpan timeSpan : spans) { + if (timeSpan.getFirstMessage().getTime() >= startTime.getTime() + && timeSpan.getLastMessage().getTime() <= endTime.getTime()) { + + result = result + timeSpan.getVsiMessageCounter(); + } + } + + return result; + } + + public synchronized int getAverageSignalStrength(Date startTime, Date endTime) { + int summedAverageSignalStrength = 0; + int numberOfVsiMessages = getNumberOfVsiMessages(startTime, endTime); + Collection spans = fixedWidthSpans.values(); + + for (TimeSpan timeSpan : spans) { + if (timeSpan.getFirstMessage().getTime() >= startTime.getTime() + && timeSpan.getLastMessage().getTime() <= endTime.getTime()) { + + int currentTimeSpanTotalSignalStrength = timeSpan.getAverageSignalStrength() * timeSpan.getVsiMessageCounter(); + summedAverageSignalStrength = summedAverageSignalStrength + currentTimeSpanTotalSignalStrength; + } + } + + if (numberOfVsiMessages > 0) { + return Math.floorDiv(summedAverageSignalStrength, numberOfVsiMessages); + } else { + return 0; + } + } + + public synchronized void addVsiMessages(int numberOfVsiMessages, int averageSignalStrength) { + int incrementedNumberOfVsiMessages = this.numberOfVsiMessages + numberOfVsiMessages; + this.averageSignalStrength = computeAverageSignalStrength(numberOfVsiMessages * averageSignalStrength, incrementedNumberOfVsiMessages); + this.numberOfVsiMessages = incrementedNumberOfVsiMessages; + } } diff --git a/src/main/java/dk/dma/ais/coverage/data/CustomMessage.java b/src/main/java/dk/dma/ais/coverage/data/CustomMessage.java index 231565a..0069d8c 100644 --- a/src/main/java/dk/dma/ais/coverage/data/CustomMessage.java +++ b/src/main/java/dk/dma/ais/coverage/data/CustomMessage.java @@ -14,16 +14,14 @@ */ package dk.dma.ais.coverage.data; +import dk.dma.ais.message.AisMessage; +import dk.dma.ais.packet.AisPacketTags.SourceType; + import java.io.Serializable; import java.util.Date; import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; import java.util.Set; -import dk.dma.ais.message.AisMessage; -import dk.dma.ais.packet.AisPacketTags.SourceType; - /** * Used for storing information relevant for the calculators */ @@ -41,6 +39,8 @@ public class CustomMessage implements Serializable { private long timeSinceLastMsg; private String key; private SourceType sourceType; + private boolean vsi; + private int signalStrength; public SourceType getSourceType() { return sourceType; @@ -125,4 +125,19 @@ public void setOriginalMessage(AisMessage originalMessage) { this.originalMessage = originalMessage; } + public void setVsi(boolean vsi) { + this.vsi = vsi; + } + + public boolean isVsi() { + return vsi; + } + + public void setSignalStrength(int signalStrength) { + this.signalStrength = signalStrength; + } + + public int getSignalStrength() { + return signalStrength; + } } diff --git a/src/main/java/dk/dma/ais/coverage/data/ICoverageData.java b/src/main/java/dk/dma/ais/coverage/data/ICoverageData.java index d378e50..e1649f5 100644 --- a/src/main/java/dk/dma/ais/coverage/data/ICoverageData.java +++ b/src/main/java/dk/dma/ais/coverage/data/ICoverageData.java @@ -14,13 +14,13 @@ */ package dk.dma.ais.coverage.data; +import dk.dma.ais.coverage.data.Ship.ShipClass; +import dk.dma.ais.packet.AisPacket; + import java.util.Collection; import java.util.Date; import java.util.List; -import dk.dma.ais.coverage.data.Ship.ShipClass; -import dk.dma.ais.packet.AisPacket; - public interface ICoverageData { CustomMessage packetToCustomMessage(AisPacket packet); @@ -30,13 +30,14 @@ public interface ICoverageData { void updateShip(Ship ship); Cell createCell(String sourceMmsi, double lat, double lon); Cell getCell(String sourceMmsi, double lat, double lon); - void updateCell(Cell c); + void updateCell(String sourceId, Cell newCell); List getCells(QueryParams params); Source getSource(String sourceId); Source createSource(String sourceId); Collection getSources(); void incrementReceivedSignals(String sourceMmsi, double lat, double lon, Date timestamp); void incrementMissingSignals(String sourceMmsi, double lat, double lon, Date timestamp); + void incrementReceivedVsiMessage(String sourceMmsi, double latitude, double longitude, Date timestamp, int signalStrength); void trimWindow(Date trimPoint); } diff --git a/src/main/java/dk/dma/ais/coverage/data/OnlyMemoryData.java b/src/main/java/dk/dma/ais/coverage/data/OnlyMemoryData.java index ec65633..8d934f1 100644 --- a/src/main/java/dk/dma/ais/coverage/data/OnlyMemoryData.java +++ b/src/main/java/dk/dma/ais/coverage/data/OnlyMemoryData.java @@ -14,34 +14,32 @@ */ package dk.dma.ais.coverage.data; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -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.ConcurrentHashMap; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import dk.dma.ais.coverage.AisCoverage; import dk.dma.ais.coverage.Helper; -import dk.dma.ais.coverage.Purger; import dk.dma.ais.coverage.calculator.AbstractCalculator; import dk.dma.ais.coverage.configuration.AisCoverageConfiguration; import dk.dma.ais.coverage.data.Ship.ShipClass; import dk.dma.ais.coverage.data.Source.ReceiverType; import dk.dma.ais.message.AisMessage; import dk.dma.ais.message.AisMessage4; -import dk.dma.ais.message.AisPositionMessage; +import dk.dma.ais.message.IVesselPositionMessage; import dk.dma.ais.packet.AisPacket; import dk.dma.ais.packet.AisPacketTags; import dk.dma.ais.packet.AisPacketTags.SourceType; import dk.dma.ais.proprietary.IProprietarySourceTag; import dk.dma.enav.model.geometry.Position; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +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.ConcurrentHashMap; public class OnlyMemoryData implements ICoverageData { private static final Logger LOG = LoggerFactory @@ -70,9 +68,52 @@ public Cell getCell(String sourceMmsi, double lat, double lon) { } @Override - public void updateCell(Cell c) { - // TODO Auto-generated method stub + public void updateCell(String sourceId, Cell newCell) { + Source source = getSource(sourceId); + if (source == null) { + source = createSource(sourceId); + } + + Cell oldCell = source.getCell(newCell.getLatitude(), newCell.getLongitude()); + if (oldCell == null) { + source.addCell(newCell); + } else { + updateExistingCellFromNewCell(oldCell, newCell); + } + } + private void updateExistingCellFromNewCell(Cell oldCell, Cell newCell) { + oldCell.addNOofMissingSignals(newCell.getNOofMissingSignals()); + oldCell.addReceivedSignals(newCell.getNOofReceivedSignals()); + + updateCellTimeSpans(oldCell, newCell); + } + + private void updateCellTimeSpans(Cell oldCell, Cell newCell) { + for (Entry newTimeSpan : newCell.getFixedWidthSpans().entrySet()) { + if (!oldCell.getFixedWidthSpans().containsKey(newTimeSpan.getKey())) { + oldCell.getFixedWidthSpans().put(newTimeSpan.getKey(), newTimeSpan.getValue()); + } else { + TimeSpan oldTimeSpan = oldCell.getFixedWidthSpans().get(newTimeSpan.getKey()); + + TimeSpan updatedTimeSpan = new TimeSpan(oldTimeSpan.getFirstMessage()); + updatedTimeSpan.setLastMessage(oldTimeSpan.getLastMessage()); + updatedTimeSpan.setMessageCounterSat(oldTimeSpan.getMessageCounterSat() + newTimeSpan.getValue().getMessageCounterSat()); + updatedTimeSpan.setMessageCounterTerrestrial(oldTimeSpan.getMessageCounterTerrestrial() + newTimeSpan.getValue().getMessageCounterTerrestrial()); + updatedTimeSpan.setMessageCounterTerrestrialUnfiltered(oldTimeSpan.getMessageCounterTerrestrialUnfiltered() + newTimeSpan.getValue().getMessageCounterTerrestrialUnfiltered()); + updatedTimeSpan.setMissingSignals(oldTimeSpan.getMissingSignals() + newTimeSpan.getValue().getMissingSignals()); + int oldVsiMessageCounter = oldTimeSpan.getVsiMessageCounter(); + int newVsiMessageCounter = newTimeSpan.getValue().getVsiMessageCounter(); + if (oldVsiMessageCounter + newVsiMessageCounter > 0) { + updatedTimeSpan.setVsiMessageCounter(oldVsiMessageCounter + newVsiMessageCounter); + updatedTimeSpan.setAverageSignalStrength( + ((oldVsiMessageCounter * oldTimeSpan.getAverageSignalStrength()) + (newVsiMessageCounter * newTimeSpan.getValue().getAverageSignalStrength())) + / updatedTimeSpan.getVsiMessageCounter()); + } + + oldCell.getFixedWidthSpans().put(newTimeSpan.getKey(), updatedTimeSpan); + } + } } @Override @@ -92,8 +133,8 @@ private List getCells() { cells.add(cell); } } - } + return cells; } @@ -145,27 +186,20 @@ private List getCells(double latStart, double lonStart, Collection bscells = source.getGrid().values(); for (Cell cell : bscells) { - if (Helper.isInsideBox(cell, latStart, lonStart, latEnd, - lonEnd)) { - Cell tempCell = cellMultiplicationSource.getCell( - cell.getLatitude(), cell.getLongitude()); + if (Helper.isInsideBox(cell, latStart, lonStart, latEnd, lonEnd)) { + Cell tempCell = cellMultiplicationSource.getCell(cell.getLatitude(), cell.getLongitude()); if (tempCell == null) { - tempCell = cellMultiplicationSource.createCell( - cell.getLatitude(), cell.getLongitude()); + tempCell = cellMultiplicationSource.createCell(cell.getLatitude(), cell.getLongitude()); } - tempCell.addNOofMissingSignals((int) cell - .getNOofMissingSignals(starttime, endtime)); - tempCell.addReceivedSignals(cell - .getNOofReceivedSignals(starttime, endtime)); + tempCell.addNOofMissingSignals((int) cell.getNOofMissingSignals(starttime, endtime)); + tempCell.addReceivedSignals(cell.getNOofReceivedSignals(starttime, endtime)); + tempCell.addVsiMessages(cell.getNumberOfVsiMessages(starttime, endtime), cell.getAverageSignalStrength(starttime, endtime)); } - } // add cells for particular source to cell-list. for (Cell cell : cellMultiplicationSource.getGrid().values()) { - if (cell.getNOofReceivedSignals() > 0) { - cells.add(cell); - } + cells.add(cell); } } } @@ -184,12 +218,23 @@ public List getCells(QueryParams params) { } @Override - public void incrementReceivedSignals(String sourceMmsi, double lat, - double lon, Date timestamp) { + public void incrementReceivedSignals(String sourceMmsi, double lat, double lon, Date timestamp) { + Cell cell = getCellFromCoordinates(sourceMmsi, lat, lon); + TimeSpan ts = getTimeSpanFromTimestamp(timestamp, cell); + + ts.setMessageCounterTerrestrial(ts.getMessageCounterTerrestrial() + 1); + cell.incrementNOofReceivedSignals(); + } + + private Cell getCellFromCoordinates(String sourceMmsi, double lat, double lon) { Cell cell = getCell(sourceMmsi, lat, lon); if (cell == null) { cell = createCell(sourceMmsi, lat, lon); } + return cell; + } + + private TimeSpan getTimeSpanFromTimestamp(Date timestamp, Cell cell) { Date id = Helper.getFloorDate(timestamp); TimeSpan ts = cell.getFixedWidthSpans().get(id.getTime()); if (ts == null) { @@ -197,27 +242,25 @@ public void incrementReceivedSignals(String sourceMmsi, double lat, ts.setLastMessage(Helper.getCeilDate(timestamp)); cell.getFixedWidthSpans().put(id.getTime(), ts); } - ts.setMessageCounterTerrestrial(ts.getMessageCounterTerrestrial() + 1); - + return ts; } @Override - public void incrementMissingSignals(String sourceMmsi, double lat, - double lon, Date timestamp) { + public void incrementMissingSignals(String sourceMmsi, double lat, double lon, Date timestamp) { + Cell cell = getCellFromCoordinates(sourceMmsi, lat, lon); + TimeSpan ts = getTimeSpanFromTimestamp(timestamp, cell); - Cell cell = getCell(sourceMmsi, lat, lon); - if (cell == null) { - cell = createCell(sourceMmsi, lat, lon); - } - Date id = Helper.getFloorDate(timestamp); - TimeSpan ts = cell.getFixedWidthSpans().get(id.getTime()); - if (ts == null) { - ts = new TimeSpan(id); - ts.setLastMessage(Helper.getCeilDate(timestamp)); - cell.getFixedWidthSpans().put(id.getTime(), ts); - } ts.incrementMissingSignals(); + cell.incrementNOofMissingSignals(); + } + + @Override + public void incrementReceivedVsiMessage(String sourceMmsi, double latitude, double longitude, Date timestamp, int signalStrength) { + Cell cell = getCellFromCoordinates(sourceMmsi, latitude, longitude); + TimeSpan ts = getTimeSpanFromTimestamp(timestamp, cell); + ts.incrementNumberOfVsiMessages(signalStrength); + cell.incrementNumberOfVsiMessages(signalStrength); } public CustomMessage packetToCustomMessage(AisPacket packet) { @@ -232,7 +275,7 @@ public CustomMessage packetToCustomMessage(AisPacket packet) { ReceiverType receiverType = ReceiverType.NOTDEFINED; Date timestamp = null; ShipClass shipClass = null; - AisPositionMessage posMessage; + IVesselPositionMessage posMessage; SourceType sourceType = SourceType.TERRESTRIAL; // Get source tag properties @@ -290,8 +333,8 @@ public CustomMessage packetToCustomMessage(AisPacket packet) { // Handle position messages. If it's not a position message // the calculators can't use them - if (aisMessage instanceof AisPositionMessage) { - posMessage = (AisPositionMessage) aisMessage; + if (aisMessage instanceof IVesselPositionMessage) { + posMessage = (IVesselPositionMessage) aisMessage; } else { return null; } @@ -319,25 +362,44 @@ public CustomMessage packetToCustomMessage(AisPacket packet) { } } - // Extract ship + Ship ship = extractShipFromMessage(aisMessage, shipClass); + + if (packet.isVsi()) { + CustomMessage newMessage = new CustomMessage(); + newMessage.setVsi(true); + newMessage.setSignalStrength(packet.getVsi().getSignalStrength()); + newMessage.setLatitude(posMessage.getPos().getGeoLocation().getLatitude()); + newMessage.setLongitude(posMessage.getPos().getGeoLocation().getLongitude()); + newMessage.setTimestamp(packet.getVsi().getTimestamp()); + + newMessage.addSourceMMSI(baseId); + newMessage.setShipMMSI(ship.getMmsi()); + newMessage.setSourceType(sourceType); + + return newMessage; + } else { + CustomMessage newMessage = new CustomMessage(); + newMessage.setCog((double) posMessage.getCog() / 10); + newMessage.setSog((double) posMessage.getSog() / 10); + newMessage.setLatitude(posMessage.getPos().getGeoLocation() + .getLatitude()); + newMessage.setLongitude(posMessage.getPos().getGeoLocation() + .getLongitude()); + newMessage.setTimestamp(timestamp); + newMessage.addSourceMMSI(baseId); + newMessage.setShipMMSI(ship.getMmsi()); + newMessage.setSourceType(sourceType); + + return newMessage; + } + } + + private Ship extractShipFromMessage(AisMessage aisMessage, ShipClass shipClass) { Ship ship = getShip(aisMessage.getUserId()); if (ship == null) { ship = createShip(aisMessage.getUserId(), shipClass); } - - CustomMessage newMessage = new CustomMessage(); - newMessage.setCog((double) posMessage.getCog() / 10); - newMessage.setSog((double) posMessage.getSog() / 10); - newMessage.setLatitude(posMessage.getPos().getGeoLocation() - .getLatitude()); - newMessage.setLongitude(posMessage.getPos().getGeoLocation() - .getLongitude()); - newMessage.setTimestamp(timestamp); - newMessage.addSourceMMSI(baseId); - newMessage.setShipMMSI(aisMessage.getUserId()); - newMessage.setSourceType(sourceType); - - return newMessage; + return ship; } @Override diff --git a/src/main/java/dk/dma/ais/coverage/data/Source.java b/src/main/java/dk/dma/ais/coverage/data/Source.java index f4cc5c1..e23db7e 100644 --- a/src/main/java/dk/dma/ais/coverage/data/Source.java +++ b/src/main/java/dk/dma/ais/coverage/data/Source.java @@ -19,7 +19,6 @@ import java.util.concurrent.ConcurrentHashMap; import dk.dma.ais.coverage.Helper; -import dk.dma.ais.coverage.data.Ship.ShipClass; public class Source implements Serializable { @@ -102,6 +101,10 @@ public Cell createTempCell(double latitude, double longitude, int multiplication return cell; } + public void addCell(Cell cell) { + grid.put(cell.getId(), cell); + } + public Map getGrid() { return grid; } diff --git a/src/main/java/dk/dma/ais/coverage/data/TimeSpan.java b/src/main/java/dk/dma/ais/coverage/data/TimeSpan.java index 2288134..21d13ab 100644 --- a/src/main/java/dk/dma/ais/coverage/data/TimeSpan.java +++ b/src/main/java/dk/dma/ais/coverage/data/TimeSpan.java @@ -26,6 +26,8 @@ public class TimeSpan { private int messageCounterTerrestrial; private int missingSignals; private int messageCounterTerrestrialUnfiltered; + private int vsiMessageCounter; + private int averageSignalStrength; public int getMessageCounterTerrestrialUnfiltered() { return messageCounterTerrestrialUnfiltered; @@ -51,6 +53,10 @@ public int getMissingSignals() { return missingSignals; } + public void setMissingSignals(int missingSignals) { + this.missingSignals = missingSignals; + } + public Map getDistinctShipsTerrestrial() { return distinctShipsTerrestrial; } @@ -96,24 +102,61 @@ public void setMessageCounterSat(int messageCounter) { this.messageCounterSat = messageCounter; } - public void add(TimeSpan span2) { - this.setMessageCounterSat(this.getMessageCounterSat() + span2.getMessageCounterSat()); - this.setMessageCounterTerrestrial(this.getMessageCounterTerrestrial() + span2.getMessageCounterTerrestrial()); - this.addMessageCounterTerrestrialUnfiltered(span2.getMessageCounterTerrestrialUnfiltered()); - for (String s : span2.distinctShipsSat.keySet()) { + public int getVsiMessageCounter() { + return vsiMessageCounter; + } + + public void setVsiMessageCounter(int vsiMessageCounter) { + this.vsiMessageCounter = vsiMessageCounter; + } + + public int getAverageSignalStrength() { + return averageSignalStrength; + } + + public void setAverageSignalStrength(int averageSignalStrength) { + this.averageSignalStrength = averageSignalStrength; + } + + public synchronized void incrementNumberOfVsiMessages(int signalStrength) { + int incrementedNumberOfVsiMessages = vsiMessageCounter + 1; + averageSignalStrength = computeAverageSignalStrength(signalStrength, incrementedNumberOfVsiMessages); + vsiMessageCounter = incrementedNumberOfVsiMessages; + } + + private int computeAverageSignalStrength(int signalStrength, int incrementedNumberOfVsiMessages) { + return Math.floorDiv((vsiMessageCounter * averageSignalStrength) + signalStrength, incrementedNumberOfVsiMessages); + } + + public void add(TimeSpan other) { + this.setMessageCounterSat(this.getMessageCounterSat() + other.getMessageCounterSat()); + this.setMessageCounterTerrestrial(this.getMessageCounterTerrestrial() + other.getMessageCounterTerrestrial()); + this.addMessageCounterTerrestrialUnfiltered(other.getMessageCounterTerrestrialUnfiltered()); + this.setAverageSignalStrength(sumAverageSignalStrength(other)); + this.setVsiMessageCounter(this.getVsiMessageCounter() + other.getVsiMessageCounter()); + for (String s : other.distinctShipsSat.keySet()) { this.distinctShipsSat.put(s, true); } - for (String s : span2.distinctShipsTerrestrial.keySet()) { + for (String s : other.distinctShipsTerrestrial.keySet()) { this.distinctShipsTerrestrial.put(s, true); } } + private int sumAverageSignalStrength(TimeSpan other) { + int thisAggregatedSignalStrength = this.getVsiMessageCounter() * this.getAverageSignalStrength(); + int otherAggregatedSignalStrength = other.getVsiMessageCounter() * other.getAverageSignalStrength(); + int summedVsiMessageCounters = this.getVsiMessageCounter() + other.getVsiMessageCounter(); + return Math.floorDiv(thisAggregatedSignalStrength + otherAggregatedSignalStrength, summedVsiMessageCounters); + } + public TimeSpan copy() { TimeSpan copy = new TimeSpan(this.getFirstMessage()); copy.setLastMessage(this.getLastMessage()); copy.setMessageCounterSat(this.getMessageCounterSat()); copy.setMessageCounterTerrestrial(this.getMessageCounterTerrestrial()); copy.setMessageCounterTerrestrialUnfiltered(this.messageCounterTerrestrialUnfiltered); + copy.setVsiMessageCounter(this.getVsiMessageCounter()); + copy.setAverageSignalStrength(this.getAverageSignalStrength()); for (String s : this.distinctShipsSat.keySet()) { copy.distinctShipsSat.put(s, true); } diff --git a/src/main/java/dk/dma/ais/coverage/export/ExportDataType.java b/src/main/java/dk/dma/ais/coverage/export/ExportDataType.java new file mode 100644 index 0000000..9251d3c --- /dev/null +++ b/src/main/java/dk/dma/ais/coverage/export/ExportDataType.java @@ -0,0 +1,28 @@ +package dk.dma.ais.coverage.export; + +/** + * Supported data types for export. + */ +public enum ExportDataType { + RECEIVED_MESSAGES(0.5, 0.2), SIGNAL_STRENGTH(-101, -107); + + private final double greenThreshold; + private final double redThreshold; + + ExportDataType(double greenThreshold, double redThreshold) { + this.greenThreshold = greenThreshold; + this.redThreshold = redThreshold; + } + + public static ExportDataType forType(String type) { + return ExportDataType.valueOf(type.toUpperCase()); + } + + public double greenThreshold() { + return greenThreshold; + } + + public double redThreshold() { + return redThreshold; + } +} diff --git a/src/main/java/dk/dma/ais/coverage/export/data/ExportCell.java b/src/main/java/dk/dma/ais/coverage/export/data/ExportCell.java index 9bd9b7d..565959a 100644 --- a/src/main/java/dk/dma/ais/coverage/export/data/ExportCell.java +++ b/src/main/java/dk/dma/ais/coverage/export/data/ExportCell.java @@ -23,6 +23,8 @@ public class ExportCell implements Serializable { public double lon; public long nrOfRecMes; public long nrOfMisMes; + public long numberOfVsiMessages; + public long averageSignalStrength; public String sourceMmsi; public double getCoverage() { diff --git a/src/main/java/dk/dma/ais/coverage/export/data/JsonConverter.java b/src/main/java/dk/dma/ais/coverage/export/data/JsonConverter.java index eb54de1..cb3b536 100644 --- a/src/main/java/dk/dma/ais/coverage/export/data/JsonConverter.java +++ b/src/main/java/dk/dma/ais/coverage/export/data/JsonConverter.java @@ -14,17 +14,13 @@ */ package dk.dma.ais.coverage.export.data; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import dk.dma.ais.coverage.calculator.AbstractCalculator; import dk.dma.ais.coverage.data.Cell; import dk.dma.ais.coverage.data.Source; import dk.dma.ais.coverage.data.TimeSpan; +import org.apache.commons.lang3.RandomUtils; public class JsonConverter { @@ -61,11 +57,13 @@ public static ExportCell toJsonCell(Cell cell, Cell superCell, Date starttime, D long expected = superCell.getNOofReceivedSignals() + superCell.getNOofMissingSignals(); // System.out.println(superCell.getNOofMissingSignals()); - ExportCell Jcell = new ExportCell(); - Jcell.lat = cell.getLatitude(); - Jcell.lon = cell.getLongitude(); - Jcell.nrOfMisMes = expected - cell.getNOofReceivedSignals(); - Jcell.nrOfRecMes = cell.getNOofReceivedSignals(); + ExportCell jsonCell = new ExportCell(); + jsonCell.lat = cell.getLatitude(); + jsonCell.lon = cell.getLongitude(); + jsonCell.nrOfMisMes = expected - cell.getNOofReceivedSignals(); + jsonCell.nrOfRecMes = cell.getNOofReceivedSignals(); + jsonCell.numberOfVsiMessages = cell.getNumberOfVsiMessages(); + jsonCell.averageSignalStrength = cell.getAverageSignalStrength(); // if(expected < Jcell.nrOfRecMes){ // System.out.println("supercell received="+superCell.getNOofReceivedSignals()); @@ -76,7 +74,7 @@ public static ExportCell toJsonCell(Cell cell, Cell superCell, Date starttime, D // System.out.println(); // } - return Jcell; + return jsonCell; } public static List toJsonTimeSpan(List timespans) { diff --git a/src/main/java/dk/dma/ais/coverage/export/generators/CSVGenerator.java b/src/main/java/dk/dma/ais/coverage/export/generators/CSVGenerator.java index 5e6449b..c16c950 100644 --- a/src/main/java/dk/dma/ais/coverage/export/generators/CSVGenerator.java +++ b/src/main/java/dk/dma/ais/coverage/export/generators/CSVGenerator.java @@ -14,20 +14,18 @@ */ package dk.dma.ais.coverage.export.generators; +import dk.dma.ais.coverage.data.Cell; +import dk.dma.ais.coverage.data.Source; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Collection; import java.util.Date; -import javax.servlet.http.HttpServletResponse; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import dk.dma.ais.coverage.data.Cell; -import dk.dma.ais.coverage.data.Source; - public class CSVGenerator { private static final Logger LOG = LoggerFactory.getLogger(KMLGenerator.class); @@ -35,7 +33,7 @@ public class CSVGenerator { public static void generateCSV(Collection grids, double latSize, double lonSize, int multiplicity, HttpServletResponse response) { - LOG.info("startet csv generation"); + LOG.info("Started CSV generation"); HttpServletResponse out = response; @@ -44,11 +42,10 @@ public static void generateCSV(Collection grids, double latSize, double String fileName = "aiscoverage-" + dateFormat.format(date) + "_latSize " + latSize + "_lonSize " + lonSize + "multiplicationfactor" + multiplicity + ".csv"; - // out.setContentType("application/vnd.google-earth.kml+xml"); out.setContentType("text/csv"); out.setHeader("Content-Disposition", "attachment; filename=" + fileName); - writeLine("latstart, longstart, latend, longend, received, missing, coverage percentage", out); + writeLine("latstart, longstart, latend, longend, received, missing, coverage percentage, receivedvsimessages, averagesignalstrength", out); for (Source grid : grids) { generateGrid(grid.getIdentifier(), grid.getGrid().values(), out, latSize * multiplicity, lonSize * multiplicity); @@ -61,7 +58,7 @@ public static void generateCSV(Collection grids, double latSize, double LOG.error(e.getMessage()); e.printStackTrace(); } - LOG.info("Finished csv generation"); + LOG.info("Finished CSV generation"); } private static void writeLine(String line, HttpServletResponse out) { @@ -74,21 +71,19 @@ private static void writeLine(String line, HttpServletResponse out) { } private static void generateGrid(String bsMmsi, Collection cells, HttpServletResponse out, double latSize, double lonSize) { - for (Cell cell : cells) { - - // We ignore cells, where average number of messages, is below 10 per ship - // Maybe there is a bug in AISMessage system, that assign some messages to wrong Base Stations - // Bug found and fixed - // if (cell.NOofReceivedSignals / cell.ships.size() > 10) { - - writeLine( - cell.getLatitude() + "," + cell.getLongitude() + "," + (cell.getLatitude() + latSize) + "," - + (cell.getLongitude() + lonSize) + "," + cell.getNOofReceivedSignals() + "," - + cell.getNOofMissingSignals() + "," + (cell.getCoverage() * 100), out); - - // } - + StringBuilder lineBuilder = new StringBuilder(); + lineBuilder.append(cell.getLatitude()).append(",") + .append(cell.getLongitude()).append(",") + .append(cell.getLatitude() + latSize).append(",") + .append(cell.getLongitude() + lonSize).append(",") + .append(cell.getNOofReceivedSignals()).append(",") + .append(cell.getNOofMissingSignals()).append(",") + .append(cell.getCoverage() * 100).append(",") + .append(cell.getNumberOfVsiMessages()).append(",") + .append(cell.getAverageSignalStrength()); + + writeLine(lineBuilder.toString(), out); } } diff --git a/src/main/java/dk/dma/ais/coverage/export/generators/KMLGenerator.java b/src/main/java/dk/dma/ais/coverage/export/generators/KMLGenerator.java index 53b91f6..a8a24ac 100644 --- a/src/main/java/dk/dma/ais/coverage/export/generators/KMLGenerator.java +++ b/src/main/java/dk/dma/ais/coverage/export/generators/KMLGenerator.java @@ -14,20 +14,19 @@ */ package dk.dma.ais.coverage.export.generators; +import dk.dma.ais.coverage.data.Cell; +import dk.dma.ais.coverage.data.Source; +import dk.dma.ais.coverage.export.ExportDataType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Collection; import java.util.Date; -import javax.servlet.http.HttpServletResponse; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import dk.dma.ais.coverage.data.Cell; -import dk.dma.ais.coverage.data.Source; - //TODO retrieve sources with larger cells. //TODO retrieve cell data from both super and individual source @@ -37,9 +36,9 @@ public class KMLGenerator { // public static void generateKML(CoverageCalculator calc, String path) { public static void generateKML(Collection grids, double latSize, double lonSize, int multiplicity, - HttpServletResponse response) { + ExportDataType exportDataType, HttpServletResponse response) { - LOG.info("startet kml generation"); + LOG.info("Started KML generation"); HttpServletResponse out = response; @@ -68,7 +67,8 @@ public static void generateKML(Collection grids, double latSize, double writeLine(" ff0000ff", out); writeLine(" ", out); writeLine(" ", out); - writeLine(" ff0000ff", out); + writeLine(" 550000ff", out); + writeLine(" 1", out); writeLine(" ", out); writeLine("", out); writeLine("", out); writeLine("", out); for (Source grid : grids) { - generateGrid(grid.getIdentifier(), grid.getGrid().values(), out, latSize * multiplicity, lonSize * multiplicity); + generateGrid(grid.getIdentifier(), grid.getGrid().values(), out, latSize * multiplicity, lonSize * multiplicity, exportDataType); } writeLine("", out); @@ -116,7 +118,7 @@ public static void generateKML(Collection grids, double latSize, double LOG.error(e.getMessage()); e.printStackTrace(); } - LOG.info("Finished kml generation"); + LOG.info("Finished KML generation"); } private static void writeLine(String line, HttpServletResponse out) { @@ -128,58 +130,54 @@ private static void writeLine(String line, HttpServletResponse out) { } } - private static void generateGrid(String bsMmsi, Collection cells, HttpServletResponse out, double latSize, double lonSize) { - + private static void generateGrid(String bsMmsi, Collection cells, HttpServletResponse out, double latSize, double lonSize, ExportDataType exportDataType) { writeLine("", out); - writeLine("" + bsMmsi + "", out); - writeLine("0", out); + writeLine(" " + bsMmsi + "", out); + writeLine(" 0", out); for (Cell cell : cells) { + double dataToExport; + if (exportDataType == ExportDataType.RECEIVED_MESSAGES) { + dataToExport = cell.getCoverage(); + } else { + dataToExport = cell.getAverageSignalStrength(); + } - // We ignore cells, where average number of messages, is below 10 per ship - // Maybe there is a bug in AISMessage system, that assign some messages to wrong Base Stations - // Bug found and fixed - // if (cell.NOofReceivedSignals / cell.ships.size() > 10) { - - if (cell.getCoverage() > 0.8) { // green - generatePlacemark("#greenStyle", cell, 300, out, latSize, lonSize); - } else if (cell.getCoverage() > 0.5) { // orange - generatePlacemark("#orangeStyle", cell, 200, out, latSize, lonSize); + if (dataToExport > exportDataType.greenThreshold()) { // green + generatePlacemark("#greenStyle", cell, 0, out, latSize, lonSize); + } else if (dataToExport > exportDataType.redThreshold()) { // orange + generatePlacemark("#orangeStyle", cell, 0, out, latSize, lonSize); } else { // red - generatePlacemark("#redStyle", cell, 100, out, latSize, lonSize); + generatePlacemark("#redStyle", cell, 0, out, latSize, lonSize); } - - // } - } writeLine("", out); - } private static void generatePlacemark(String style, Cell cell, int z, HttpServletResponse out, double latSize, double lonSize) { - writeLine("", out); - writeLine("" + cell.getId() + "", out); - writeLine("" + style + "", out); - writeLine("", out); - writeLine("relativeToGround", out); - writeLine("1", out); - writeLine("", out); - writeLine("", out); - writeLine("", out); + writeLine(" ", out); + writeLine(" " + cell.getId() + "", out); + writeLine(" " + style + "", out); + writeLine(" ", out); + writeLine(" clampedToGround", out); + writeLine(" 1", out); + writeLine(" ", out); + writeLine(" ", out); + writeLine(" ", out); writeLine( - cell.getLongitude() + "," + cell.getLatitude() + "," + z + " " + (cell.getLongitude() + lonSize) + "," - + cell.getLatitude() + "," + z + " " + (cell.getLongitude() + lonSize) + "," - + (cell.getLatitude() + latSize) + "," + z + " " + cell.getLongitude() + "," - + (cell.getLatitude() + latSize) + "," + z, out); - - writeLine("", out); - writeLine("", out); - writeLine("", out); - writeLine("", out); - writeLine("", out); - + cell.getLongitude() + "," + cell.getLatitude() + "," + z + " " + + (cell.getLongitude() + lonSize) + "," + cell.getLatitude() + "," + z + " " + + (cell.getLongitude() + lonSize) + "," + (cell.getLatitude() + latSize) + "," + z + " " + + cell.getLongitude() + "," + (cell.getLatitude() + latSize) + "," + z + " " + + cell.getLongitude() + "," + cell.getLatitude() + "," + z, out); + + writeLine(" ", out); + writeLine(" ", out); + writeLine(" ", out); + writeLine(" ", out); + writeLine(" ", out); } } diff --git a/src/main/java/dk/dma/ais/coverage/export/generators/XMLGenerator.java b/src/main/java/dk/dma/ais/coverage/export/generators/XMLGenerator.java index 2c3e843..5f77940 100644 --- a/src/main/java/dk/dma/ais/coverage/export/generators/XMLGenerator.java +++ b/src/main/java/dk/dma/ais/coverage/export/generators/XMLGenerator.java @@ -14,28 +14,25 @@ */ package dk.dma.ais.coverage.export.generators; +import dk.dma.ais.coverage.data.Cell; +import dk.dma.ais.coverage.data.Source; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Collection; import java.util.Date; -import javax.servlet.http.HttpServletResponse; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import dk.dma.ais.coverage.data.Cell; -import dk.dma.ais.coverage.data.Source; - public class XMLGenerator { - private static final Logger LOG = LoggerFactory.getLogger(XMLGenerator.class); public static void generateXML(Collection grids, double latSize, double lonSize, int multiplicity, HttpServletResponse response) { - LOG.info("startet csv generation"); + LOG.info("Started XML generation"); HttpServletResponse out = response; @@ -63,7 +60,7 @@ public static void generateXML(Collection grids, double latSize, double LOG.error(e.getMessage()); e.printStackTrace(); } - LOG.info("Finished csv generation"); + LOG.info("Finished XML generation"); } private static void writeLine(String line, HttpServletResponse out) { @@ -76,25 +73,18 @@ private static void writeLine(String line, HttpServletResponse out) { } private static void generateGrid(String bsMmsi, Collection cells, HttpServletResponse out, double latSize, double lonSize) { - for (Cell cell : cells) { - - // We ignore cells, where average number of messages, is below 10 per ship - // Maybe there is a bug in AISMessage system, that assign some messages to wrong Base Stations - // Bug found and fixed - // if (cell.NOofReceivedSignals / cell.ships.size() > 10) { - writeLine("", out); - writeLine("" + cell.getLatitude() + "", out); - writeLine("" + cell.getLongitude() + "", out); - writeLine("" + (cell.getLatitude() + latSize) + "", out); - writeLine("" + (cell.getLongitude() + lonSize) + "", out); - writeLine("" + cell.getNOofReceivedSignals() + "", out); - writeLine("" + cell.getNOofMissingSignals() + "", out); - writeLine("" + (cell.getCoverage() * 100) + "", out); + writeLine(" " + cell.getLatitude() + "", out); + writeLine(" " + cell.getLongitude() + "", out); + writeLine(" " + (cell.getLatitude() + latSize) + "", out); + writeLine(" " + (cell.getLongitude() + lonSize) + "", out); + writeLine(" " + cell.getNOofReceivedSignals() + "", out); + writeLine(" " + cell.getNOofMissingSignals() + "", out); + writeLine(" " + (cell.getCoverage() * 100) + "", out); + writeLine(" " + cell.getNumberOfVsiMessages() + "", out); + writeLine(" " + cell.getAverageSignalStrength() + "", out); writeLine("", out); - - // } } } } diff --git a/src/main/java/dk/dma/ais/coverage/persistence/CoverageDataMarshaller.java b/src/main/java/dk/dma/ais/coverage/persistence/CoverageDataMarshaller.java new file mode 100644 index 0000000..ff9c0f7 --- /dev/null +++ b/src/main/java/dk/dma/ais/coverage/persistence/CoverageDataMarshaller.java @@ -0,0 +1,17 @@ +package dk.dma.ais.coverage.persistence; + +import java.time.ZonedDateTime; +import java.util.Collection; +import java.util.Map; + +import dk.dma.ais.coverage.data.Cell; + +/** + * Implementations are responsible for marshalling and unmarshalling coverage data to and from a database typing system. + */ +interface CoverageDataMarshaller { + + T marshall(Map> coverageData, ZonedDateTime dataTimestamp); + + Map> unmarshall(T coverageData); +} diff --git a/src/main/java/dk/dma/ais/coverage/persistence/DatabaseConnectionException.java b/src/main/java/dk/dma/ais/coverage/persistence/DatabaseConnectionException.java new file mode 100644 index 0000000..f5ae737 --- /dev/null +++ b/src/main/java/dk/dma/ais/coverage/persistence/DatabaseConnectionException.java @@ -0,0 +1,11 @@ +package dk.dma.ais.coverage.persistence; + +/** + * Indicates that the system could not connect to a given database. + */ +public class DatabaseConnectionException extends RuntimeException { + + public DatabaseConnectionException(String errorMessage, Throwable cause) { + super(errorMessage, cause); + } +} diff --git a/src/main/java/dk/dma/ais/coverage/persistence/DatabaseInstance.java b/src/main/java/dk/dma/ais/coverage/persistence/DatabaseInstance.java new file mode 100644 index 0000000..9a62e88 --- /dev/null +++ b/src/main/java/dk/dma/ais/coverage/persistence/DatabaseInstance.java @@ -0,0 +1,46 @@ +package dk.dma.ais.coverage.persistence; + +import java.util.Collection; +import java.util.Map; + +import dk.dma.ais.coverage.configuration.DatabaseConfiguration; +import dk.dma.ais.coverage.data.Cell; + +/** + * Data layer abstraction. Implementations care for the details of each operation for a specific database type. + */ +public interface DatabaseInstance extends AutoCloseable { + + /** + * Opens the connection to the database based on the provided configuration. + * Any opened instance should be closed using {@link #close()}. + * + * @param configuration + * the database configuration parameters + * @throws DatabaseConnectionException + * when connection to the database server is impossible for any reason + */ + void open(DatabaseConfiguration configuration); + + /** + * Creates the database where coverage data is collected if required. + * + * @throws DatabaseConnectionException + * when connection to the database server is impossible for any reason + */ + void createDatabase(); + + /** + * Saves the coverage data stored in in-memory cells to the underlying database. + * + * @param coverageData + * the coverage data stored as a {@link Collection} of {@link Cell} by source identifier + * @return + * the result of the save operation + * @throws DatabaseConnectionException + * when connection to the database server is impossible for any reason + */ + PersistenceResult save(Map> coverageData); + + Map> loadLatestSavedCoverageData(); +} diff --git a/src/main/java/dk/dma/ais/coverage/persistence/DatabaseInstanceFactory.java b/src/main/java/dk/dma/ais/coverage/persistence/DatabaseInstanceFactory.java new file mode 100644 index 0000000..c51f79f --- /dev/null +++ b/src/main/java/dk/dma/ais/coverage/persistence/DatabaseInstanceFactory.java @@ -0,0 +1,9 @@ +package dk.dma.ais.coverage.persistence; + +/** + * Implementations of this factory type create new {@link DatabaseInstance} implementations instances. + */ +public interface DatabaseInstanceFactory { + + DatabaseInstance createDatabaseInstance(String databaseType); +} diff --git a/src/main/java/dk/dma/ais/coverage/persistence/MarshallingException.java b/src/main/java/dk/dma/ais/coverage/persistence/MarshallingException.java new file mode 100644 index 0000000..b7a162e --- /dev/null +++ b/src/main/java/dk/dma/ais/coverage/persistence/MarshallingException.java @@ -0,0 +1,13 @@ +package dk.dma.ais.coverage.persistence; + +import java.io.IOException; + +/** + * Indicates an exception while marshalling coverage data. + */ +public class MarshallingException extends RuntimeException { + + public MarshallingException(String message, IOException cause) { + super(message, cause); + } +} diff --git a/src/main/java/dk/dma/ais/coverage/persistence/MemoryOnlyDatabaseInstance.java b/src/main/java/dk/dma/ais/coverage/persistence/MemoryOnlyDatabaseInstance.java new file mode 100644 index 0000000..d15b307 --- /dev/null +++ b/src/main/java/dk/dma/ais/coverage/persistence/MemoryOnlyDatabaseInstance.java @@ -0,0 +1,36 @@ +package dk.dma.ais.coverage.persistence; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import dk.dma.ais.coverage.configuration.DatabaseConfiguration; +import dk.dma.ais.coverage.data.Cell; + +/** + * {@link DatabaseInstance} implementation that keeps data in memory. Data is never persisted anywhere. + */ +class MemoryOnlyDatabaseInstance implements DatabaseInstance { + + @Override + public void open(DatabaseConfiguration configuration) { + } + + @Override + public void createDatabase() { + } + + @Override + public PersistenceResult save(Map> coverageData) { + return null; + } + + @Override + public Map> loadLatestSavedCoverageData() { + return Collections.emptyMap(); + } + + @Override + public void close() throws Exception { + } +} diff --git a/src/main/java/dk/dma/ais/coverage/persistence/MongoCoverageDataMarshaller.java b/src/main/java/dk/dma/ais/coverage/persistence/MongoCoverageDataMarshaller.java new file mode 100644 index 0000000..1b42f4e --- /dev/null +++ b/src/main/java/dk/dma/ais/coverage/persistence/MongoCoverageDataMarshaller.java @@ -0,0 +1,199 @@ +package dk.dma.ais.coverage.persistence; + +import dk.dma.ais.coverage.Helper; +import dk.dma.ais.coverage.data.Cell; +import dk.dma.ais.coverage.data.TimeSpan; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.bson.Document; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collection; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +/** + * A {@link CoverageDataMarshaller} that marshalls coverage data in a format usable to store in a MongoDB database. + */ +class MongoCoverageDataMarshaller implements CoverageDataMarshaller { + + @Override + public Document marshall(Map> coverageData, ZonedDateTime dataTimestamp) { + Document coverageDataDocument = new Document(); + + coverageDataDocument.put("dataTimestamp", dataTimestamp.withZoneSameInstant(ZoneId.of("UTC")).format(DateTimeFormatter.ISO_DATE_TIME)); + + Document documentToBeZipped = new Document(); + List> grid = new ArrayList<>(); + if (coverageData != null) { + marshallCells(coverageData, grid); + } + documentToBeZipped.put("cells", grid); + + String compressedCells = compressCellsData(documentToBeZipped); + coverageDataDocument.put("compressedCells", compressedCells); + coverageDataDocument.put("numberOfCells", grid.size()); + + return coverageDataDocument; + } + + private void marshallCells(Map> coverageData, List> grid) { + for (String sourceId : coverageData.keySet()) { + for (Cell cell : coverageData.get(sourceId)) { + Map savedCell = marshallCell(sourceId, cell); + grid.add(savedCell); + } + } + } + + private Map marshallCell(String sourceId, Cell cell) { + Map savedCell = new LinkedHashMap<>(); + savedCell.put("sourceId", sourceId); + savedCell.put("cellId", cell.getId()); + savedCell.put("latitude", cell.getLatitude()); + savedCell.put("longitude", cell.getLongitude()); + savedCell.put("numberOfReceivedSignals", cell.getNOofReceivedSignals()); + savedCell.put("numberOfMissingSignals", cell.getNOofMissingSignals()); + savedCell.put("numberOfVsiMessages", cell.getNumberOfVsiMessages()); + savedCell.put("averageSignalStrength", cell.getAverageSignalStrength()); + + Map> fixedWidthTimeSpans = marshallCellTimeSpans(cell); + savedCell.put("timespans", fixedWidthTimeSpans); + + return savedCell; + } + + private Map> marshallCellTimeSpans(Cell cell) { + Map> fixedWidthTimeSpans = new LinkedHashMap<>(); + for (Map.Entry fixedWidthTimeSpan : cell.getFixedWidthSpans().entrySet()) { + Map messages = new LinkedHashMap<>(); + messages.put("firstMessage", fixedWidthTimeSpan.getValue().getFirstMessage().getTime()); + messages.put("lastMessage", fixedWidthTimeSpan.getValue().getLastMessage().getTime()); + messages.put("messageCounterSat", fixedWidthTimeSpan.getValue().getMessageCounterSat()); + messages.put("messageCounterTerrestrial", fixedWidthTimeSpan.getValue().getMessageCounterTerrestrial()); + messages.put("messageCounterTerrestrialUnfiltered", fixedWidthTimeSpan.getValue().getMessageCounterTerrestrialUnfiltered()); + messages.put("missingSignals", fixedWidthTimeSpan.getValue().getMissingSignals()); + messages.put("vsiMessageCounter", fixedWidthTimeSpan.getValue().getVsiMessageCounter()); + messages.put("averageSignalStrength", fixedWidthTimeSpan.getValue().getAverageSignalStrength()); + + fixedWidthTimeSpans.put(fixedWidthTimeSpan.getKey().toString(), messages); + } + + return fixedWidthTimeSpans; + } + + private String compressCellsData(Document documentToBeZipped) { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + + try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream);) { + gzipOutputStream.write(documentToBeZipped.toJson().getBytes(StandardCharsets.US_ASCII)); + gzipOutputStream.close(); + + byte[] bytes = byteArrayOutputStream.toByteArray(); + return new String(Base64.getEncoder().encode(bytes), StandardCharsets.US_ASCII); + } catch (IOException e) { + throw new MarshallingException("Could not compress cells data", e); + } + } + + @Override + public Map> unmarshall(Document coverageData) { + Map> unmarshalledCoverageData = new LinkedHashMap<>(); + + if (coverageData != null) { + unmarshalledCoverageData.putAll(unmarshallCells(coverageData)); + } + + return unmarshalledCoverageData; + } + + private Map> unmarshallCells(Document coverageData) { + Map> unmarshalledCoverageData = new LinkedHashMap<>(); + Document decompressedCells = decompressCells(coverageData); + Object cells = decompressedCells.get("cells"); + + if (cells != null && (cells instanceof List)) { + List> grid = (List>) cells; + for (Map cell : grid) { + Cell unmarshalledCell = unmarshallCell(cell); + String sourceId = (String) cell.get("sourceId"); + if (unmarshalledCoverageData.containsKey(sourceId)) { + unmarshalledCoverageData.get(sourceId).add(unmarshalledCell); + } else { + unmarshalledCoverageData.put(sourceId, new ArrayList<>(Arrays.asList(unmarshalledCell))); + } + } + } + + return unmarshalledCoverageData; + } + + private Document decompressCells(Document marshalledCoverageData) { + String base64 = (String) marshalledCoverageData.get("compressedCells"); + if (!StringUtils.isBlank(base64)) { + byte[] gzippedData = Base64.getDecoder().decode(base64.getBytes(StandardCharsets.US_ASCII)); + + try (GZIPInputStream gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(gzippedData))) { + String jsonData = IOUtils.toString(gzipInputStream, StandardCharsets.US_ASCII); + return Document.parse(jsonData); + } catch (IOException e) { + throw new MarshallingException("Could not unmarshall compressed cells data", e); + } + } + + return new Document(); + } + + private Cell unmarshallCell(Map cell) { + double latitude = Helper.roundLat((double) cell.get("latitude"), 1); + double longitude = Helper.roundLon((double) cell.get("longitude"), 1); + Cell unmarshalledCell = new Cell(latitude, longitude, (String) cell.get("cellId")); + unmarshalledCell.addReceivedSignals(((Integer) cell.get("numberOfReceivedSignals")).intValue()); + unmarshalledCell.addNOofMissingSignals(((Integer) cell.get("numberOfMissingSignals")).intValue()); + + Integer numberOfVsiMessages = (Integer) cell.get("numberOfVsiMessages"); + if (numberOfVsiMessages != null && numberOfVsiMessages > 0) { + unmarshalledCell.addVsiMessages(numberOfVsiMessages.intValue(), ((Integer) cell.get("averageSignalStrength")).intValue()); + } + + Map> fixedWidthTimeSpans = (Map>) cell.get("timespans"); + Map unmarshalledTimeSpans = unmarshallTimeSpans(fixedWidthTimeSpans); + + unmarshalledCell.setFixedWidthSpans(unmarshalledTimeSpans); + return unmarshalledCell; + } + + private Map unmarshallTimeSpans(Map> fixedWidthTimeSpans) { + Map unmarshalledTimeSpans = new LinkedHashMap<>(); + for (Map.Entry> timespan : fixedWidthTimeSpans.entrySet()) { + TimeSpan unmarshalledTimeSpan = new TimeSpan(new Date(timespan.getValue().get("firstMessage").longValue())); + unmarshalledTimeSpan.setLastMessage(new Date(timespan.getValue().get("lastMessage").longValue())); + unmarshalledTimeSpan.setMessageCounterSat(timespan.getValue().get("messageCounterSat").intValue()); + unmarshalledTimeSpan.setMessageCounterTerrestrial(timespan.getValue().get("messageCounterTerrestrial").intValue()); + unmarshalledTimeSpan.setMessageCounterTerrestrialUnfiltered(timespan.getValue().get("messageCounterTerrestrialUnfiltered").intValue()); + unmarshalledTimeSpan.setMissingSignals(timespan.getValue().get("missingSignals").intValue()); + + Integer numberOfVsiMessages = (Integer) timespan.getValue().get("vsiMessageCounter"); + if (numberOfVsiMessages != null) { + unmarshalledTimeSpan.setVsiMessageCounter(numberOfVsiMessages.intValue()); + unmarshalledTimeSpan.setAverageSignalStrength(((Integer) timespan.getValue().get("averageSignalStrength")).intValue()); + } + + unmarshalledTimeSpans.put(Long.valueOf(timespan.getKey()), unmarshalledTimeSpan); + } + return unmarshalledTimeSpans; + } +} diff --git a/src/main/java/dk/dma/ais/coverage/persistence/MongoDatabaseInstance.java b/src/main/java/dk/dma/ais/coverage/persistence/MongoDatabaseInstance.java new file mode 100644 index 0000000..81dc5d1 --- /dev/null +++ b/src/main/java/dk/dma/ais/coverage/persistence/MongoDatabaseInstance.java @@ -0,0 +1,164 @@ +package dk.dma.ais.coverage.persistence; + +import com.mongodb.MongoClient; +import com.mongodb.MongoClientOptions; +import com.mongodb.MongoException; +import com.mongodb.ServerAddress; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoDatabase; +import dk.dma.ais.coverage.configuration.DatabaseConfiguration; +import dk.dma.ais.coverage.data.Cell; +import org.bson.Document; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * {@link DatabaseInstance} implementation that keeps data in a MongoDB database. + */ +class MongoDatabaseInstance implements DatabaseInstance { + private static final Logger LOG = LoggerFactory.getLogger(MongoDatabaseInstance.class); + private static final String COVERAGE_DATA = "coverageData"; + + private CoverageDataMarshaller marshaller; + private String mongoServerHost; + private int mongoServerPort; + private String databaseName; + private MongoClientOptions mongoClientOptions = MongoClientOptions.builder().build(); + private MongoClient client; + + public MongoDatabaseInstance(CoverageDataMarshaller marshaller) { + this.marshaller = marshaller; + } + + @Override + public void open(DatabaseConfiguration configuration) { + mongoServerHost = configuration.getAddr(); + mongoServerPort = configuration.getPort(); + databaseName = configuration.getDbName(); + + LOG.info("Establishing connection with MongoDB database"); + + try { + client = connectToMongoServer(); + LOG.info("Connection established with MongoDB database"); + } catch (MongoException e) { + logAndTransformException(e); + } + } + + private MongoClient connectToMongoServer() { + ServerAddress serverAddress = new ServerAddress(mongoServerHost, mongoServerPort); + return new MongoClient(serverAddress, mongoClientOptions); + } + + private void logAndTransformException(Exception cause) throws DatabaseConnectionException { + String errorMessage = String.format("Could not connect to MongoDB database [%s] at [%s:%d]", databaseName, mongoServerHost, mongoServerPort); + LOG.error(errorMessage, cause); + throw new DatabaseConnectionException(errorMessage, cause); + } + + @Override + public void close() throws Exception { + if (client != null) { + LOG.info("Closing connection with MongoDB database"); + client.close(); + LOG.info("Connection with MongoDB database closed"); + } + } + + @Override + public void createDatabase() { + requireOpenConnection(); + + try { + createCoverageDataCollection(); + } catch (MongoException e) { + logAndTransformException(e); + } + } + + private void requireOpenConnection() { + if (client == null) { + throw new IllegalStateException("DatabaseInstance must be opened before executing operations: #open() should be invoked first."); + } + } + + private void createCoverageDataCollection() { + MongoDatabase database = client.getDatabase(databaseName); + if (!collectionExists(database)) { + database.createCollection(COVERAGE_DATA); + } + } + + private boolean collectionExists(MongoDatabase database) { + for (String collectionName : database.listCollectionNames()) { + if (COVERAGE_DATA.equalsIgnoreCase(collectionName)) { + return true; + } + } + + return false; + } + + @Override + public PersistenceResult save(Map> coverageData) { + requireOpenConnection(); + + Document coverageDataDocument = marshaller.marshall(coverageData, ZonedDateTime.now(ZoneId.of("UTC"))); + long numberOfSavedCells = ((Integer) coverageDataDocument.get("numberOfCells")); + + try { + client.getDatabase(databaseName).getCollection(COVERAGE_DATA).insertOne(coverageDataDocument); + LOG.info("Saved [{}] cells with timestamp [{}]", numberOfSavedCells, coverageDataDocument.get("dataTimestamp")); + return PersistenceResult.success(numberOfSavedCells); + } catch (MongoException | MarshallingException e) { + logAndTransformException(e); + } + + return PersistenceResult.failure(); + } + + @Override + public Map> loadLatestSavedCoverageData() { + requireOpenConnection(); + + Map> latestCoverageData = new LinkedHashMap<>(); + + try { + Document orderByDataTimestamp = new Document(); + orderByDataTimestamp.put("dataTimestamp", -1); + + FindIterable foundDocuments = client.getDatabase(databaseName).getCollection(COVERAGE_DATA).find().sort(orderByDataTimestamp).limit(1); + if (foundDocuments.iterator().hasNext()) { + Document document = foundDocuments.iterator().next(); + latestCoverageData.putAll(marshaller.unmarshall(document)); + + long loadedCells = 0L; + for (Collection cells : latestCoverageData.values()) { + loadedCells = loadedCells + cells.size(); + } + + LOG.info("Loaded [{}] cells from previously saved state at [{}]", loadedCells, document.get("dataTimestamp")); + } + } catch (MongoException | MarshallingException e) { + logAndTransformException(e); + } + + return Collections.unmodifiableMap(latestCoverageData); + } + + void setMongoClientOptions(MongoClientOptions mongoClientOptions) { + this.mongoClientOptions = mongoClientOptions; + } + + void setMarshaller(MongoCoverageDataMarshaller marshaller) { + this.marshaller = marshaller; + } +} diff --git a/src/main/java/dk/dma/ais/coverage/persistence/PersistenceResult.java b/src/main/java/dk/dma/ais/coverage/persistence/PersistenceResult.java new file mode 100644 index 0000000..1f5d82b --- /dev/null +++ b/src/main/java/dk/dma/ais/coverage/persistence/PersistenceResult.java @@ -0,0 +1,40 @@ +package dk.dma.ais.coverage.persistence; + +/** + * Represents the result of saving a list of cells to a database. + */ +public class PersistenceResult { + + public enum Status { + SUCCESS, FAILURE + } + + public static PersistenceResult success(long writtenCells) { + PersistenceResult result = new PersistenceResult(); + result.status = Status.SUCCESS; + result.writtenCells = writtenCells; + + return result; + } + + public static PersistenceResult failure() { + PersistenceResult result = new PersistenceResult(); + result.status = Status.FAILURE; + + return result; + } + + private Status status; + private long writtenCells; + + private PersistenceResult() { + } + + public Status getStatus() { + return status; + } + + public long getWrittenCells() { + return writtenCells; + } +} diff --git a/src/main/java/dk/dma/ais/coverage/persistence/PersisterService.java b/src/main/java/dk/dma/ais/coverage/persistence/PersisterService.java new file mode 100644 index 0000000..fcdd2d3 --- /dev/null +++ b/src/main/java/dk/dma/ais/coverage/persistence/PersisterService.java @@ -0,0 +1,101 @@ +package dk.dma.ais.coverage.persistence; + +import dk.dma.ais.coverage.data.Cell; +import dk.dma.ais.coverage.data.ICoverageData; +import dk.dma.ais.coverage.data.Source; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.Duration; +import java.time.Instant; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * Performs asynchronous save operation to configured database. + */ +public class PersisterService { + private static final Logger LOG = LoggerFactory.getLogger(PersisterService.class); + + private final DatabaseInstance databaseInstance; + private final ICoverageData coverageData; + private long persistenceIntervalInMinutes = 60; + private ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + + public PersisterService(DatabaseInstance databaseInstance, ICoverageData coverageData) { + this.databaseInstance = databaseInstance; + this.coverageData = coverageData; + } + + public void start() { + LOG.info("Starting PersisterService, persisting every [{}] minutes", persistenceIntervalInMinutes); + + executor.scheduleAtFixedRate(new SaveOperation(), persistenceIntervalInMinutes, persistenceIntervalInMinutes, TimeUnit.MINUTES); + + LOG.info("PersisterService started"); + } + + public void stop() { + LOG.info("Stopping PersisterService"); + + executor.shutdown(); + try { + if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { + executor.shutdownNow(); + if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { + LOG.warn("PersisterService thread pool did not terminate cleanly"); + } + } + } catch (InterruptedException e) { + executor.shutdownNow(); + Thread.currentThread().interrupt(); + } + + LOG.info("PersisterService stopped"); + } + + public void intervalInMinutes(long persistenceIntervalInMinutes) { + this.persistenceIntervalInMinutes = persistenceIntervalInMinutes; + } + + long getIntervalInMinutes() { + return persistenceIntervalInMinutes; + } + + void setExecutor(ScheduledExecutorService executor) { + this.executor = executor; + } + + private class SaveOperation implements Runnable { + + @Override + public void run() { + LOG.info("Starting save operation..."); + Instant start = Instant.now(); + Map> cellsBySource = new LinkedHashMap<>(); + for (Source source : coverageData.getSources()) { + cellsBySource.put(source.getIdentifier(), source.getGrid().values()); + } + + PersistenceResult persistenceResult = null; + try { + persistenceResult = databaseInstance.save(cellsBySource); + } catch (RuntimeException e) { + LOG.error("Error while saving coverage data", e); + } + Instant end = Instant.now(); + + if ((persistenceResult != null) && PersistenceResult.Status.SUCCESS.equals(persistenceResult.getStatus())) { + LOG.info("Saved [{}] cells to MongoDB database", persistenceResult.getWrittenCells()); + } else { + LOG.info("Failed saving cells to MongoDB database"); + } + + LOG.info("Save operation took [{}] ms", Duration.between(start, end).toMillis()); + } + } +} diff --git a/src/main/java/dk/dma/ais/coverage/persistence/TypeBasedDatabaseInstanceFactory.java b/src/main/java/dk/dma/ais/coverage/persistence/TypeBasedDatabaseInstanceFactory.java new file mode 100644 index 0000000..86ed739 --- /dev/null +++ b/src/main/java/dk/dma/ais/coverage/persistence/TypeBasedDatabaseInstanceFactory.java @@ -0,0 +1,20 @@ +package dk.dma.ais.coverage.persistence; + +/** + * This {@link DatabaseInstanceFactory} implementation supports creating {@link DatabaseInstance} instances for MongoDB + * or MemoryOnly. + */ +public class TypeBasedDatabaseInstanceFactory implements DatabaseInstanceFactory { + + @Override + public DatabaseInstance createDatabaseInstance(String databaseType) { + if ("MemoryOnly".equalsIgnoreCase(databaseType)) { + return new MemoryOnlyDatabaseInstance(); + } else if ("MongoDB".equalsIgnoreCase(databaseType)) { + MongoCoverageDataMarshaller marshaller = new MongoCoverageDataMarshaller(); + return new MongoDatabaseInstance(marshaller); + } else { + throw new UnknownDatabaseTypeException(String.format("Unsupported database type: [%s]", databaseType)); + } + } +} diff --git a/src/main/java/dk/dma/ais/coverage/persistence/UnknownDatabaseTypeException.java b/src/main/java/dk/dma/ais/coverage/persistence/UnknownDatabaseTypeException.java new file mode 100644 index 0000000..e610c03 --- /dev/null +++ b/src/main/java/dk/dma/ais/coverage/persistence/UnknownDatabaseTypeException.java @@ -0,0 +1,11 @@ +package dk.dma.ais.coverage.persistence; + +/** + * Indicates that an unknown or unsupported database type has been specified. + */ +public class UnknownDatabaseTypeException extends RuntimeException { + + public UnknownDatabaseTypeException(String message) { + super(message); + } +} diff --git a/src/main/java/dk/dma/ais/coverage/rest/CoverageRestService.java b/src/main/java/dk/dma/ais/coverage/rest/CoverageRestService.java index ab9ae0f..68c72e9 100644 --- a/src/main/java/dk/dma/ais/coverage/rest/CoverageRestService.java +++ b/src/main/java/dk/dma/ais/coverage/rest/CoverageRestService.java @@ -14,34 +14,6 @@ */ package dk.dma.ais.coverage.rest; -import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; - -import javax.servlet.ServletOutputStream; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -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.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.UriInfo; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -//import dk.dma.ais.coverage.export.CSVGenerator; import dk.dma.ais.coverage.AisCoverage; import dk.dma.ais.coverage.CoverageHandler; import dk.dma.ais.coverage.Helper; @@ -51,14 +23,43 @@ import dk.dma.ais.coverage.data.OnlyMemoryData; import dk.dma.ais.coverage.data.Source; import dk.dma.ais.coverage.data.TimeSpan; +import dk.dma.ais.coverage.export.ExportDataType; import dk.dma.ais.coverage.export.data.ExportShipTimeSpan; import dk.dma.ais.coverage.export.data.JSonCoverageMap; import dk.dma.ais.coverage.export.data.JsonConverter; import dk.dma.ais.coverage.export.data.JsonSource; import dk.dma.ais.coverage.export.data.Status; +import dk.dma.ais.coverage.export.generators.CSVGenerator; import dk.dma.ais.coverage.export.generators.ChartGenerator; import dk.dma.ais.coverage.export.generators.KMLGenerator; import dk.dma.ais.coverage.export.generators.XMLGenerator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +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.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.UriInfo; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +//import dk.dma.ais.coverage.export.CSVGenerator; /** * JAX-RS rest services @@ -124,8 +125,6 @@ public JSonCoverageMap coverage(@Context HttpServletRequest request) { String area = request.getParameter("area"); long starttime = Long.parseLong(request.getParameter("starttime")); long endtime = Long.parseLong(request.getParameter("endtime")); -// System.out.println(starttime); -// System.out.println(endtime); String[] areaArray = area.split(","); @@ -155,13 +154,13 @@ public JSonCoverageMap coverage(@Context HttpServletRequest request) { @Path("export") @Produces(MediaType.APPLICATION_JSON) public Object export(@QueryParam("exportType") String exportType, @QueryParam("exportMultiFactor") String exportMultiFactor, - @Context HttpServletResponse response, @QueryParam("startTime") String startTime, @QueryParam("endTime") String endTime) { - // return null; + @Context HttpServletResponse response, @QueryParam("startTime") String startTime, @QueryParam("endTime") String endTime, + @QueryParam("exportDataType") String exportDataType) { + int multiplicity = Integer.parseInt(exportMultiFactor); - long starttime = Long.parseLong(startTime); - long endtime = Long.parseLong(endTime); -// System.out.println(starttime); -// System.out.println(endtime); + Date starttime = new Date(Long.parseLong(startTime)); + Date endtime = new Date(Long.parseLong(endTime)); + ExportDataType dataType = ExportDataType.forType(exportDataType); // // // BaseStationHandler gh = new BaseStationHandler(); @@ -199,26 +198,23 @@ public Object export(@QueryParam("exportType") String exportType, @QueryParam("e Cell activesbscell = superbs.getGrid().get(cell.getId()); if (activesbscell != null) { - int receivedsignals = cell.getNOofReceivedSignals(new Date(starttime), new Date(endtime)); + int receivedsignals = cell.getNOofReceivedSignals(starttime, endtime); dhCell.addReceivedSignals(receivedsignals); - int sbstotalmessages = activesbscell.getNOofReceivedSignals(new Date(starttime), new Date(endtime)) - + activesbscell.getNOofMissingSignals(new Date(starttime), new Date(endtime)); + + int sbstotalmessages = activesbscell.getNOofReceivedSignals(starttime, endtime) + + activesbscell.getNOofMissingSignals(starttime, endtime); dhCell.addNOofMissingSignals(sbstotalmessages - receivedsignals); - } - // LOG.debug("cell for export created: " + summedbs.getCell(cell.getLatitude(), - // cell.getLongitude()).getNOofReceivedSignals() + "-" + summedbs.getCell(cell.getLatitude(), - // cell.getLongitude()).getNOofMissingSignals()); + dhCell.addVsiMessages(activesbscell.getNumberOfVsiMessages(starttime, endtime), + activesbscell.getAverageSignalStrength(starttime, endtime)); + } } } if (exportType.equals("KML")) { - // System.out.println(expotype); - KMLGenerator.generateKML(dh.getSources(), AisCoverage.get().getConf().getLatSize(), AisCoverage.get().getConf().getLonSize(), multiplicity, response); + KMLGenerator.generateKML(dh.getSources(), AisCoverage.get().getConf().getLatSize(), AisCoverage.get().getConf().getLonSize(), multiplicity, dataType, response); } else if (exportType.equals("CSV")) { - // System.out.println(expotype); - // CSVGenerator.generateCSV(dh.getSources(), Helper.latSize, Helper.lonSize, multiplicity, response); + CSVGenerator.generateCSV(dh.getSources(), AisCoverage.get().getConf().getLatSize(), AisCoverage.get().getConf().getLonSize(), multiplicity, response); } else if (exportType.equals("XML")) { - // System.out.println(expotype); XMLGenerator.generateXML(dh.getSources(), AisCoverage.get().getConf().getLatSize(), AisCoverage.get().getConf().getLonSize(), multiplicity, response); } else { System.out.println("wrong exporttype"); @@ -425,15 +421,39 @@ public Object satExport(@QueryParam("test") String test, @Context HttpServletRes @Produces(MediaType.APPLICATION_JSON) public Object status() throws IOException { LOG.info("getting status"); - Date first = Helper.firstMessage; - Date last = Helper.latestMessage; - Status s = new Status(); - s.firstMessage = first.getTime(); - s.lastMessage = last.getTime(); - s.analysisStatus = "Running"; - return s; + Status status = new Status(); + Date now = Helper.getFloorDate(new Date()); + + setFirstMessageTimestamp(status, now); + setLastMessageTimestamp(status, now); + + status.analysisStatus = "Running"; + + return status; + } + + private void setFirstMessageTimestamp(Status status, Date now) { + if (messageReceived()) { + status.firstMessage = Helper.firstMessage.getTime(); + } else { + status.firstMessage = now.getTime(); + } + } + private boolean messageReceived() { + return Helper.firstMessage != null; } + private void setLastMessageTimestamp(Status status, Date now) { + if (terrestrialMessageReceived()) { + status.lastMessage = Helper.latestMessage.getTime(); + } else { + status.lastMessage = now.getTime(); + } + } + + private boolean terrestrialMessageReceived() { + return Helper.latestMessage != null; + } } diff --git a/src/main/resources/coverage-mongodb-sample.xml b/src/main/resources/coverage-mongodb-sample.xml index e82f0f3..017d8dc 100644 --- a/src/main/resources/coverage-mongodb-sample.xml +++ b/src/main/resources/coverage-mongodb-sample.xml @@ -18,6 +18,7 @@ xsi:type="distributerConsumerConfiguration"> UNFILTERED + 100000 @@ -33,6 +34,7 @@ localhost nordicCoverage 27017 + 60 diff --git a/src/main/resources/log4j.xml b/src/main/resources/log4j.xml index 6248868..2d51908 100644 --- a/src/main/resources/log4j.xml +++ b/src/main/resources/log4j.xml @@ -19,7 +19,21 @@ - + + + + + + + + + + + + + + + @@ -27,13 +41,17 @@ + + + + + - \ No newline at end of file diff --git a/src/test/java/dk/dma/ais/coverage/AisCoverageBuilder.java b/src/test/java/dk/dma/ais/coverage/AisCoverageBuilder.java new file mode 100644 index 0000000..6ca1b51 --- /dev/null +++ b/src/test/java/dk/dma/ais/coverage/AisCoverageBuilder.java @@ -0,0 +1,97 @@ +package dk.dma.ais.coverage; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Objects; + +import dk.dma.ais.bus.AisBus; +import dk.dma.ais.configuration.bus.AisBusConfiguration; +import dk.dma.ais.coverage.configuration.AisCoverageConfiguration; +import dk.dma.ais.coverage.configuration.DatabaseConfiguration; +import dk.dma.ais.coverage.persistence.DatabaseInstance; +import dk.dma.ais.coverage.persistence.DatabaseInstanceFactory; +import dk.dma.ais.coverage.persistence.PersisterService; + +/** + * Builder class that builds test instances of {@link AisCoverage}. + */ +public class AisCoverageBuilder { + private static final String MISSING_COVERAGE_CONFIGURATION_ERROR_MESSAGE = "An AisCoverageConfiguration instance is required. Consider invoking #withMockAisCoverageConfiguration"; + + private AisCoverageConfiguration aisCoverageConfiguration; + private DatabaseInstanceFactory mockingDatabaseInstanceFactory; + private PersisterService persisterService; + + public AisCoverageBuilder withMockAisCoverageConfiguration() { + aisCoverageConfiguration = mock(AisCoverageConfiguration.class); + + return this; + } + + public AisCoverageBuilder withMockAisBusConfiguration() { + Objects.requireNonNull(aisCoverageConfiguration, MISSING_COVERAGE_CONFIGURATION_ERROR_MESSAGE); + + AisBus aisBus = mock(AisBus.class); + AisBusConfiguration aisBusConfiguration = mock(AisBusConfiguration.class); + when(aisBusConfiguration.getInstance()).thenReturn(aisBus); + when(aisCoverageConfiguration.getAisbusConfiguration()).thenReturn(aisBusConfiguration); + + return this; + } + + public AisCoverageBuilder withDatabaseConfiguration(DatabaseConfiguration databaseConfiguration) { + Objects.requireNonNull(aisCoverageConfiguration, MISSING_COVERAGE_CONFIGURATION_ERROR_MESSAGE); + + when(aisCoverageConfiguration.getDatabaseConfiguration()).thenReturn(databaseConfiguration); + + return this; + } + + public AisCoverageBuilder withMockDatabaseConfiguration() { + Objects.requireNonNull(aisCoverageConfiguration, MISSING_COVERAGE_CONFIGURATION_ERROR_MESSAGE); + + DatabaseConfiguration mockDatabaseConfiguration = new DatabaseConfiguration(); + when(aisCoverageConfiguration.getDatabaseConfiguration()).thenReturn(mockDatabaseConfiguration); + + return this; + } + + public AisCoverageBuilder withDatabaseInstanceForType(DatabaseInstance databaseInstance, String databaseType) { + mockingDatabaseInstanceFactory = mock(DatabaseInstanceFactory.class); + when(mockingDatabaseInstanceFactory.createDatabaseInstance(eq(databaseType))).thenReturn(databaseInstance); + + return this; + } + + public AisCoverageBuilder withMockDatabaseInstanceForAnyType() { + mockingDatabaseInstanceFactory = mock(DatabaseInstanceFactory.class); + DatabaseInstance mockDatabaseInstance = mock(DatabaseInstance.class); + when(mockingDatabaseInstanceFactory.createDatabaseInstance(anyString())).thenReturn(mockDatabaseInstance); + + return this; + } + + public AisCoverageBuilder withPersisterService(PersisterService persisterService) { + this.persisterService = persisterService; + + return this; + } + + public AisCoverage build() { + if (aisCoverageConfiguration == null) { + withMockAisCoverageConfiguration(); + withMockAisBusConfiguration(); + } + + AisCoverage aisCoverage = AisCoverage.create(aisCoverageConfiguration, mockingDatabaseInstanceFactory); + + if (persisterService != null) { + aisCoverage.setPersisterService(persisterService); + } + + return aisCoverage; + } +} diff --git a/src/test/java/dk/dma/ais/coverage/AisCoverageTest.java b/src/test/java/dk/dma/ais/coverage/AisCoverageTest.java new file mode 100644 index 0000000..792c1ef --- /dev/null +++ b/src/test/java/dk/dma/ais/coverage/AisCoverageTest.java @@ -0,0 +1,89 @@ +package dk.dma.ais.coverage; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import org.junit.Test; + +import dk.dma.ais.coverage.configuration.DatabaseConfiguration; +import dk.dma.ais.coverage.persistence.DatabaseInstance; +import dk.dma.ais.coverage.persistence.PersisterService; + +public class AisCoverageTest { + + @Test + public void whenNewInstance_thenDatabaseIsCreatedAndExistingDataIsLoaded() { + DatabaseConfiguration databaseConfiguration = new DatabaseConfiguration(); + databaseConfiguration.setType("MongoDB"); + + DatabaseInstance mockDatabaseInstance = mock(DatabaseInstance.class); + + aisCoverageWithMockDatabaseInstance(databaseConfiguration, mockDatabaseInstance); + + verify(mockDatabaseInstance).open(databaseConfiguration); + verify(mockDatabaseInstance).createDatabase(); + verify(mockDatabaseInstance).loadLatestSavedCoverageData(); + } + + private AisCoverage aisCoverageWithMockDatabaseInstance(DatabaseConfiguration databaseConfiguration, DatabaseInstance mockDatabaseInstance) { + AisCoverageBuilder builder = new AisCoverageBuilder(); + builder.withMockAisCoverageConfiguration() + .withDatabaseConfiguration(databaseConfiguration) + .withMockAisBusConfiguration() + .withDatabaseInstanceForType(mockDatabaseInstance, "MongoDB"); + + return builder.build(); + } + + @Test + public void whenStart_thenPersisterServiceIsStarted() { + PersisterService persisterService = mock(PersisterService.class); + AisCoverage aisCoverage = aisCoverageWithMockPersisterService(persisterService); + + aisCoverage.start(); + + verify(persisterService).start(); + } + + private AisCoverage aisCoverageWithMockPersisterService(PersisterService persisterService) { + AisCoverageBuilder builder = new AisCoverageBuilder(); + return builder + .withMockAisCoverageConfiguration() + .withMockAisBusConfiguration() + .withMockDatabaseConfiguration() + .withMockDatabaseInstanceForAnyType() + .withPersisterService(persisterService).build(); + } + + @Test + public void whenStop_thenPersisterServiceIsStopped() { + PersisterService persisterService = mock(PersisterService.class); + AisCoverage aisCoverage = aisCoverageWithMockPersisterService(persisterService); + + aisCoverage.stop(); + + verify(persisterService).stop(); + } + + @Test + public void whenStop_thenDatabaseInstanceIsClosed() throws Exception { + DatabaseConfiguration databaseConfiguration = new DatabaseConfiguration(); + databaseConfiguration.setType("MongoDB"); + + DatabaseInstance mockDatabaseInstance = mock(DatabaseInstance.class); + + PersisterService persisterService = mock(PersisterService.class); + + AisCoverageBuilder builder = new AisCoverageBuilder(); + AisCoverage aisCoverage = builder + .withMockAisCoverageConfiguration() + .withMockAisBusConfiguration() + .withDatabaseConfiguration(databaseConfiguration) + .withDatabaseInstanceForType(mockDatabaseInstance, "MongoDB") + .withPersisterService(persisterService).build(); + + aisCoverage.stop(); + + verify(mockDatabaseInstance).close(); + } +} diff --git a/src/test/java/dk/dma/ais/coverage/PurgerTest.java b/src/test/java/dk/dma/ais/coverage/PurgerTest.java new file mode 100644 index 0000000..dba3de0 --- /dev/null +++ b/src/test/java/dk/dma/ais/coverage/PurgerTest.java @@ -0,0 +1,27 @@ +package dk.dma.ais.coverage; + +import dk.dma.commons.util.DateTimeUtil; +import org.apache.commons.lang3.time.DateUtils; +import org.joda.time.PeriodType; +import org.junit.Test; + +import java.util.Date; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +public class PurgerTest { + + @Test + public void givenLargeWindowSize_whenGetTrimPoint_windowSizeCorrectlyConvertedToHours() { + Purger purger = new Purger(1000, null, 1); + Helper.firstMessage = new Date(); + final int maxWindowSize = 1000; + Helper.latestMessage = DateUtils.addHours(new Date(), 2 * maxWindowSize); + + Date trimPoint = purger.getTrimPoint(); + + assertThat(DateTimeUtil.toInterval(Helper.firstMessage, trimPoint).toPeriod(PeriodType.hours()).getHours(), is(equalTo(maxWindowSize))); + } +} diff --git a/src/test/java/dk/dma/ais/coverage/configuration/AisCoverageConfigurationTest.java b/src/test/java/dk/dma/ais/coverage/configuration/AisCoverageConfigurationTest.java new file mode 100644 index 0000000..ec3006a --- /dev/null +++ b/src/test/java/dk/dma/ais/coverage/configuration/AisCoverageConfigurationTest.java @@ -0,0 +1,24 @@ +package dk.dma.ais.coverage.configuration; + +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +public class AisCoverageConfigurationTest { + + @Test + public void whenNewInstance_thenMessageBufferSizeDefaultsTo10000() { + AisCoverageConfiguration configuration = new AisCoverageConfiguration(); + + assertThat(configuration.getMessageBufferSize(), is(equalTo(10000))); + } + + @Test + public void whenNewInstance_thenReceivedPacketsBufferSizeDefaultsTo10000() { + AisCoverageConfiguration configuration = new AisCoverageConfiguration(); + + assertThat(configuration.getReceivedPacketsBufferSize(), is(equalTo(10000))); + } +} diff --git a/src/test/java/dk/dma/ais/coverage/configuration/DatabaseConfigurationTest.java b/src/test/java/dk/dma/ais/coverage/configuration/DatabaseConfigurationTest.java new file mode 100644 index 0000000..4dee186 --- /dev/null +++ b/src/test/java/dk/dma/ais/coverage/configuration/DatabaseConfigurationTest.java @@ -0,0 +1,17 @@ +package dk.dma.ais.coverage.configuration; + +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +public class DatabaseConfigurationTest { + + @Test + public void whenNewInstance_thenPersistenceIntervalInMinutesDefaultsTo60() { + DatabaseConfiguration configuration = new DatabaseConfiguration(); + + assertThat(configuration.getPersistenceIntervalInMinutes(), is(equalTo(60))); + } +} diff --git a/src/test/java/dk/dma/ais/coverage/data/CellTest.java b/src/test/java/dk/dma/ais/coverage/data/CellTest.java new file mode 100644 index 0000000..16009fd --- /dev/null +++ b/src/test/java/dk/dma/ais/coverage/data/CellTest.java @@ -0,0 +1,71 @@ +package dk.dma.ais.coverage.data; + +import dk.dma.ais.coverage.Helper; +import dk.dma.ais.coverage.configuration.AisCoverageConfiguration; +import dk.dma.ais.coverage.fixture.CellFixture; +import org.junit.Before; +import org.junit.Test; + +import java.util.Iterator; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +public class CellTest { + + @Before + public void setUp() throws Exception { + Helper.conf = new AisCoverageConfiguration(); + } + + @Test + public void whenIncrementNumberOfVsiMessages_thenNumberOfVsiMessagesHasOneAdded() { + Cell aCell = CellFixture.createCellWithNoTimeSpan(); + + aCell.incrementNumberOfVsiMessages(-10); + aCell.incrementNumberOfVsiMessages(-20); + + assertThat(aCell.getNumberOfVsiMessages(), is(equalTo(2))); + assertThat(aCell.getAverageSignalStrength(), is(equalTo(-15))); + } + + @Test + public void whenAddVsiMessages_thenNumberOfVsiMessagesAreSummedAndAverageSignalStrengthIsRecomputed() { + Cell aCell = CellFixture.createCellWithNoTimeSpan(); + + aCell.incrementNumberOfVsiMessages(-10); + aCell.incrementNumberOfVsiMessages(-20); + + aCell.addVsiMessages(3, -12); + + assertThat(aCell.getNumberOfVsiMessages(), is(equalTo(5))); + assertThat(aCell.getAverageSignalStrength(), is(equalTo(-14))); + } + + @Test + public void whenGetNumberOfVsiMessagesWithTimestamps_thenNumberOfVsiMessagesFromMatchingTimespansAreReturned() { + Cell aCell = CellFixture.createCellWithTimeSpans(); + TimeSpan firstTimespan = aCell.getFixedWidthSpans().values().iterator().next(); + + int numberOfVsiMessages = aCell.getNumberOfVsiMessages(firstTimespan.getFirstMessage(), firstTimespan.getLastMessage()); + + assertThat(numberOfVsiMessages, is(equalTo(firstTimespan.getVsiMessageCounter()))); + } + + @Test + public void whenGetAverageSignalStrength_thenAverageSignalStrengthFromMatchingTimespansAreReturned() { + Cell aCell = CellFixture.createCellWithTimeSpans(); + Iterator timeSpanIterator = aCell.getFixedWidthSpans().values().iterator(); + TimeSpan firstTimespan = timeSpanIterator.next(); + TimeSpan secondTimespan = timeSpanIterator.next(); + + int averageSignalStrength = aCell.getAverageSignalStrength(firstTimespan.getFirstMessage(), secondTimespan.getLastMessage()); + + int firstTimespanAverageSignalStrength = firstTimespan.getAverageSignalStrength() * firstTimespan.getVsiMessageCounter(); + int secondTimespanAverageSignalStrength = secondTimespan.getAverageSignalStrength() * secondTimespan.getVsiMessageCounter(); + int totalVsiMessagesForBothTimespans = firstTimespan.getVsiMessageCounter() + secondTimespan.getVsiMessageCounter(); + int expectedAverageSignalStrength = Math.floorDiv(firstTimespanAverageSignalStrength + secondTimespanAverageSignalStrength, totalVsiMessagesForBothTimespans); + assertThat(averageSignalStrength, is(equalTo(expectedAverageSignalStrength))); + } +} diff --git a/src/test/java/dk/dma/ais/coverage/data/OnlyMemoryDataTest.java b/src/test/java/dk/dma/ais/coverage/data/OnlyMemoryDataTest.java new file mode 100644 index 0000000..b4e69ea --- /dev/null +++ b/src/test/java/dk/dma/ais/coverage/data/OnlyMemoryDataTest.java @@ -0,0 +1,48 @@ +package dk.dma.ais.coverage.data; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Date; + +import org.junit.Before; +import org.junit.Test; + +import dk.dma.ais.coverage.Helper; +import dk.dma.ais.coverage.calculator.AbstractCalculator; +import dk.dma.ais.coverage.configuration.AisCoverageConfiguration; +import dk.dma.ais.coverage.fixture.CellFixture; + +public class OnlyMemoryDataTest { + + private ICoverageData coverageData; + private Cell aCell; + private Date now; + + @Before + public void setUp() throws Exception { + Helper.conf = new AisCoverageConfiguration(); + + coverageData = new OnlyMemoryData(); + aCell = CellFixture.createCellWithNoTimeSpan(); + coverageData.updateCell(AbstractCalculator.SUPERSOURCE_MMSI, aCell); + now = new Date(ZonedDateTime.now(ZoneId.of("UTC")).toInstant().getEpochSecond()); + } + + @Test + public void whenIncrementMissingSignals_thenCellGlobalMissingSignalsAreIncremented() { + coverageData.incrementMissingSignals(AbstractCalculator.SUPERSOURCE_MMSI, aCell.getLatitude(), aCell.getLongitude(), now); + + assertThat(aCell.getNOofMissingSignals(), is(equalTo(1))); + } + + @Test + public void whenIncrementReceivedSignals_thenCellGlobalReceivedSignalsAreIncremented() { + coverageData.incrementReceivedSignals(AbstractCalculator.SUPERSOURCE_MMSI, aCell.getLatitude(), aCell.getLongitude(), now); + + assertThat(aCell.getNOofReceivedSignals(), is(equalTo(1))); + } +} diff --git a/src/test/java/dk/dma/ais/coverage/data/TimeSpanTest.java b/src/test/java/dk/dma/ais/coverage/data/TimeSpanTest.java new file mode 100644 index 0000000..a4747ca --- /dev/null +++ b/src/test/java/dk/dma/ais/coverage/data/TimeSpanTest.java @@ -0,0 +1,69 @@ +package dk.dma.ais.coverage.data; + +import org.junit.Before; +import org.junit.Test; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Date; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +public class TimeSpanTest { + private Date now; + private TimeSpan aTimeSpan; + private TimeSpan anotherTimeSpan; + + @Before + public void setUp() throws Exception { + now = new Date(ZonedDateTime.now(ZoneId.of("UTC")).toInstant().toEpochMilli()); + aTimeSpan = new TimeSpan(now); + anotherTimeSpan = new TimeSpan(now); + } + + @Test + public void whenAdd_thenVsiMessageCounterIsSumOfCurrentAndAddedTimeSpan() { + aTimeSpan.setVsiMessageCounter(5); + anotherTimeSpan.setVsiMessageCounter(5); + + aTimeSpan.add(anotherTimeSpan); + + assertThat(aTimeSpan.getVsiMessageCounter(), is(equalTo(10))); + } + + @Test + public void whenAdd_thenAverageSignalStrengthComputesAddedTimeSpan() { + aTimeSpan.setVsiMessageCounter(9); + aTimeSpan.setAverageSignalStrength(0); + anotherTimeSpan.setVsiMessageCounter(1); + anotherTimeSpan.setAverageSignalStrength(-100); + + aTimeSpan.add(anotherTimeSpan); + + assertThat(aTimeSpan.getAverageSignalStrength(), is(equalTo(-10))); + } + + @Test + public void whenCopy_thenCopyHasSameVsiMessageCounterAndAverageSignalStrength() { + aTimeSpan.setVsiMessageCounter(9); + aTimeSpan.setAverageSignalStrength(-100); + + TimeSpan copy = aTimeSpan.copy(); + + assertThat(copy.getVsiMessageCounter(), is(equalTo(9))); + assertThat(copy.getAverageSignalStrength(), is(equalTo(-100))); + } + + @Test + public void whenIncrementNumberOfVsiMessages_thenNumberOfVsiMessagesHasOneAdded() { + aTimeSpan.setVsiMessageCounter(1); + aTimeSpan.setAverageSignalStrength(-15); + + aTimeSpan.incrementNumberOfVsiMessages(-10); + + assertThat(aTimeSpan.getVsiMessageCounter(), is(equalTo(2))); + assertThat(aTimeSpan.getAverageSignalStrength(), is(equalTo(-13))); + } +} diff --git a/src/test/java/dk/dma/ais/coverage/export/ExportDataTypeTest.java b/src/test/java/dk/dma/ais/coverage/export/ExportDataTypeTest.java new file mode 100644 index 0000000..7f1d141 --- /dev/null +++ b/src/test/java/dk/dma/ais/coverage/export/ExportDataTypeTest.java @@ -0,0 +1,34 @@ +package dk.dma.ais.coverage.export; + +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.closeTo; +import static org.junit.Assert.assertThat; + +public class ExportDataTypeTest { + + @Test + public void givenReceivedMessages_whenValueOf_thenReceivedMessagesIsReturned() { + ExportDataType exportDataType = ExportDataType.forType("received_messages"); + + assertThat(exportDataType, is(ExportDataType.RECEIVED_MESSAGES)); + } + + @Test + public void givenSignalStrength_whenValueOf_thensignalStrengthIsReturned() { + ExportDataType exportDataType = ExportDataType.forType("signal_strength"); + + assertThat(exportDataType, is(ExportDataType.SIGNAL_STRENGTH)); + } + + @Test + public void givenReceivedMessages_whenGreenThreshold_thenValueIs50Percent() { + assertThat(ExportDataType.RECEIVED_MESSAGES.greenThreshold(), is(closeTo(0.5, 0.001))); + } + + @Test + public void givenReceivedMessages_whenRedThreshold_thenValueIs20Percent() { + assertThat(ExportDataType.RECEIVED_MESSAGES.redThreshold(), is(closeTo(0.2, 0.001))); + } +} diff --git a/src/test/java/dk/dma/ais/coverage/fixture/CellFixture.java b/src/test/java/dk/dma/ais/coverage/fixture/CellFixture.java new file mode 100644 index 0000000..f343b19 --- /dev/null +++ b/src/test/java/dk/dma/ais/coverage/fixture/CellFixture.java @@ -0,0 +1,71 @@ +package dk.dma.ais.coverage.fixture; + +import dk.dma.ais.coverage.Helper; +import dk.dma.ais.coverage.calculator.geotools.SphereProjection; +import dk.dma.ais.coverage.data.Cell; +import dk.dma.ais.coverage.data.TimeSpan; +import org.apache.commons.lang3.RandomUtils; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Map; + +public class CellFixture { + + public static Cell createCellWithTimeSpans() { + Cell cell = createCell(); + + Map fixedWidthTimeSpans = createTimeSpans(); + cell.setFixedWidthSpans(fixedWidthTimeSpans); + + return cell; + } + + private static Cell createCell() { + double latitude = randomLatitude(); + double longitude = randomLongitude(latitude); + String cellId = Helper.getCellId(latitude, longitude, 1); + + Cell cell = new Cell(latitude, longitude, cellId); + return cell; + } + + public static double randomLatitude() { + return Helper.roundLat(SphereProjection.metersToLatDegree(RandomUtils.nextDouble()), 1); + } + + public static double randomLongitude(double latitude) { + return Helper.roundLon(SphereProjection.metersToLonDegree(latitude, RandomUtils.nextDouble()), 1); + } + + private static Map createTimeSpans() { + Map fixedWidthTimeSpans = new LinkedHashMap<>(); + + for (int i = 0; i <= 1; i++) { + long timespanKey = ZonedDateTime.now(ZoneId.of("UTC")).toInstant().toEpochMilli() + (i * 10000); + TimeSpan timespan = new TimeSpan(new Date(timespanKey)); + timespan.setLastMessage(new Date(timespanKey + ((i + 1) * 10000))); + timespan.setMessageCounterSat(RandomUtils.nextInt()); + timespan.setMessageCounterTerrestrial(RandomUtils.nextInt()); + timespan.setMessageCounterTerrestrialUnfiltered(RandomUtils.nextInt()); + timespan.setMissingSignals(RandomUtils.nextInt()); + timespan.setVsiMessageCounter(RandomUtils.nextInt(1, 10)); + timespan.setAverageSignalStrength(Math.negateExact(RandomUtils.nextInt(10, 60))); + + fixedWidthTimeSpans.put(timespanKey, timespan); + } + + return fixedWidthTimeSpans; + } + + public static Cell createCellWithNoTimeSpan() { + Cell cell = createCell(); + + Map fixedWidthTimeSpans = new LinkedHashMap<>(); + cell.setFixedWidthSpans(fixedWidthTimeSpans); + + return cell; + } +} diff --git a/src/test/java/dk/dma/ais/coverage/persistence/MemoryOnlyDatabaseInstanceTest.java b/src/test/java/dk/dma/ais/coverage/persistence/MemoryOnlyDatabaseInstanceTest.java new file mode 100644 index 0000000..bcd58e2 --- /dev/null +++ b/src/test/java/dk/dma/ais/coverage/persistence/MemoryOnlyDatabaseInstanceTest.java @@ -0,0 +1,34 @@ +package dk.dma.ais.coverage.persistence; + +import static java.util.Collections.emptyMap; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertThat; + +import java.util.Collection; +import java.util.Map; + +import org.junit.Test; + +import dk.dma.ais.coverage.data.Cell; + +public class MemoryOnlyDatabaseInstanceTest { + + @Test + public void whenSave_thenNullPersistenceResultIsReturned() { + DatabaseInstance databaseInstance = new MemoryOnlyDatabaseInstance(); + + PersistenceResult persistenceResult = databaseInstance.save(emptyMap()); + + assertThat(persistenceResult, is(nullValue())); + } + + @Test + public void whenLoadLatestSavedCoverageData_thenEmptyListIsReturned() { + DatabaseInstance databaseInstance = new MemoryOnlyDatabaseInstance(); + + Map> coverageData = databaseInstance.loadLatestSavedCoverageData(); + + assertThat(coverageData.isEmpty(), is(true)); + } +} diff --git a/src/test/java/dk/dma/ais/coverage/persistence/MongoCoverageDataMarshallerTest.java b/src/test/java/dk/dma/ais/coverage/persistence/MongoCoverageDataMarshallerTest.java new file mode 100644 index 0000000..67a6aed --- /dev/null +++ b/src/test/java/dk/dma/ais/coverage/persistence/MongoCoverageDataMarshallerTest.java @@ -0,0 +1,215 @@ +package dk.dma.ais.coverage.persistence; + +import dk.dma.ais.coverage.Helper; +import dk.dma.ais.coverage.configuration.AisCoverageConfiguration; +import dk.dma.ais.coverage.data.Cell; +import dk.dma.ais.coverage.data.TimeSpan; +import dk.dma.ais.coverage.fixture.CellFixture; +import org.apache.commons.io.IOUtils; +import org.bson.Document; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.GZIPInputStream; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.closeTo; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +public class MongoCoverageDataMarshallerTest { + private static final double DELTA_WHEN_COMPARING_DOUBLE = 0.0001D; + private MongoCoverageDataMarshaller marshaller; + + @Before + public void setUp() throws Exception { + marshaller = new MongoCoverageDataMarshaller(); + + Helper.conf = new AisCoverageConfiguration(); + } + + @After + public void tearDown() throws Exception { + Helper.conf = null; + } + + @Test + public void givenNoCoverageData_whenMarshall_thenDocumentWithEmptyCellsArrayAndTimestampIsReturned() { + ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")); + + Document marshalledCoverageData = marshaller.marshall(null, now); + + assertThatDocumentHasEmptyCellsAndDataTimestamp(marshalledCoverageData, now); + + marshalledCoverageData = marshaller.marshall(Collections.emptyMap(), now); + + assertThatDocumentHasEmptyCellsAndDataTimestamp(marshalledCoverageData, now); + } + + private void assertThatDocumentHasEmptyCellsAndDataTimestamp(Document marshalledCoverageData, ZonedDateTime now) { + ZonedDateTime dataTimestamp = ZonedDateTime.parse((String) marshalledCoverageData.get("dataTimestamp"), DateTimeFormatter.ISO_DATE_TIME); + assertThat(dataTimestamp, is(equalTo(now))); + + Document decompressedCells = decompressCells(marshalledCoverageData); + assertThat(((List>) decompressedCells.get("cells")).isEmpty(), is(true)); + } + + private Document decompressCells(Document marshalledCoverageData) { + String base64 = (String) marshalledCoverageData.get("compressedCells"); + byte[] gzippedData = Base64.getDecoder().decode(base64.getBytes(StandardCharsets.US_ASCII)); + + try (GZIPInputStream gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(gzippedData))) { + String jsonData = IOUtils.toString(gzipInputStream, StandardCharsets.US_ASCII); + return Document.parse(jsonData); + } catch (IOException e) { + fail("Failed decompressing cells: " + e.getMessage()); + } + + return null; + } + + @Test + public void givenManyCells_whenMarshall_thenDocumentWithTheseCellsAndTimestampIsReturned() { + ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")); + + List cells = new ArrayList<>(); + Cell firstCell = CellFixture.createCellWithTimeSpans(); + Cell secondCell = CellFixture.createCellWithNoTimeSpan(); + cells.add(firstCell); + cells.add(secondCell); + Map> cellsBySourceId = new LinkedHashMap<>(); + cellsBySourceId.put("default", cells); + + Document marshalledCoverageData = marshaller.marshall(cellsBySourceId, now); + + ZonedDateTime dataTimestamp = ZonedDateTime.parse((String) marshalledCoverageData.get("dataTimestamp"), DateTimeFormatter.ISO_DATE_TIME); + assertThat(dataTimestamp, is(equalTo(now))); + + Document decompressedCells = decompressCells(marshalledCoverageData); + List> grid = (List>) decompressedCells.get("cells"); + assertThat(grid.size(), is(equalTo(2))); + + Map firstMarshalledCell = grid.get(0); + assertThat(firstMarshalledCell.get("cellId"), is(equalTo(firstCell.getId()))); + assertThat(firstMarshalledCell.get("latitude"), is(equalTo(firstCell.getLatitude()))); + assertThat(firstMarshalledCell.get("longitude"), is(equalTo(firstCell.getLongitude()))); + assertThat(firstMarshalledCell.get("numberOfReceivedSignals"), is(equalTo(firstCell.getNOofReceivedSignals()))); + assertThat(firstMarshalledCell.get("numberOfMissingSignals"), is(equalTo(firstCell.getNOofMissingSignals()))); + assertThat(firstMarshalledCell.get("numberOfVsiMessages"), is(equalTo(firstCell.getNumberOfVsiMessages()))); + assertThat(firstMarshalledCell.get("averageSignalStrength"), is(equalTo(firstCell.getAverageSignalStrength()))); + + Map> firstMarshalledCellTimespans = (Map>) firstMarshalledCell.get("timespans"); + assertThat(firstMarshalledCellTimespans.size(), is(equalTo(2))); + + for (Long timespanKey : firstCell.getFixedWidthSpans().keySet()) { + TimeSpan expectedTimeSpan = firstCell.getFixedWidthSpans().get(timespanKey); + Map marshalledTimeSpan = firstMarshalledCellTimespans.get(timespanKey.toString()); + + assertThat(marshalledTimeSpan.get("firstMessage"), is(equalTo(expectedTimeSpan.getFirstMessage().getTime()))); + assertThat(marshalledTimeSpan.get("lastMessage"), is(equalTo(expectedTimeSpan.getLastMessage().getTime()))); + assertThat(marshalledTimeSpan.get("messageCounterSat"), is(equalTo(expectedTimeSpan.getMessageCounterSat()))); + assertThat(marshalledTimeSpan.get("messageCounterTerrestrial"), is(equalTo(expectedTimeSpan.getMessageCounterTerrestrial()))); + assertThat(marshalledTimeSpan.get("messageCounterTerrestrialUnfiltered"), is(equalTo(expectedTimeSpan.getMessageCounterTerrestrialUnfiltered()))); + assertThat(marshalledTimeSpan.get("missingSignals"), is(equalTo(expectedTimeSpan.getMissingSignals()))); + assertThat(marshalledTimeSpan.get("vsiMessageCounter"), is(equalTo(expectedTimeSpan.getVsiMessageCounter()))); + assertThat(marshalledTimeSpan.get("averageSignalStrength"), is(equalTo(expectedTimeSpan.getAverageSignalStrength()))); + } + + Map secondMarshalledCell = grid.get(1); + assertThat(secondMarshalledCell.get("cellId"), is(equalTo(secondCell.getId()))); + assertThat(secondMarshalledCell.get("latitude"), is(equalTo(secondCell.getLatitude()))); + assertThat(secondMarshalledCell.get("longitude"), is(equalTo(secondCell.getLongitude()))); + assertThat(secondMarshalledCell.get("numberOfReceivedSignals"), is(equalTo(secondCell.getNOofReceivedSignals()))); + assertThat(secondMarshalledCell.get("numberOfMissingSignals"), is(equalTo(secondCell.getNOofMissingSignals()))); + assertThat(secondMarshalledCell.get("numberOfVsiMessages"), is(equalTo(secondCell.getNumberOfVsiMessages()))); + assertThat(secondMarshalledCell.get("averageSignalStrength"), is(equalTo(secondCell.getAverageSignalStrength()))); + + Map> secondMarshalledCellTimespans = (Map>) secondMarshalledCell.get("timespans"); + assertThat(secondMarshalledCellTimespans.isEmpty(), is(true)); + } + + @Test + public void givenNoCoverageData_whenUnmarshall_thenEmptyListIsReturned() { + Map> coverageData = marshaller.unmarshall(null); + assertThat(coverageData.isEmpty(), is(true)); + + coverageData = marshaller.unmarshall(new Document()); + assertThat(coverageData.isEmpty(), is(true)); + + Document documentWithEmptyCells = new Document(); + documentWithEmptyCells.put("cells", Collections.emptyMap()); + coverageData = marshaller.unmarshall(documentWithEmptyCells); + assertThat(coverageData.isEmpty(), is(true)); + } + + @Test + public void givenManyCells_whenUnmarshall_thenTheseCellsAreReturned() { + ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")); + List cells = new ArrayList<>(); + Cell firstCell = CellFixture.createCellWithTimeSpans(); + Cell secondCell = CellFixture.createCellWithNoTimeSpan(); + cells.add(firstCell); + cells.add(secondCell); + Map> cellsBySourceId = new LinkedHashMap<>(); + cellsBySourceId.put("default", cells); + Document marshalledCoverageData = marshaller.marshall(cellsBySourceId, now); + + Map> unmarshalledCoverageData = marshaller.unmarshall(marshalledCoverageData); + + assertThat(unmarshalledCoverageData.get("default").size(), is(equalTo(2))); + + Iterator cellsFromDefaultSource = unmarshalledCoverageData.get("default").iterator(); + Cell firstUnmarshalledCell = cellsFromDefaultSource.next(); + assertThat(firstUnmarshalledCell.getId(), is(equalTo(firstCell.getId()))); + assertThat(firstUnmarshalledCell.getLatitude(), is(closeTo(firstCell.getLatitude(), DELTA_WHEN_COMPARING_DOUBLE))); + assertThat(firstUnmarshalledCell.getLongitude(), is(closeTo(firstCell.getLongitude(), DELTA_WHEN_COMPARING_DOUBLE))); + assertThat(firstUnmarshalledCell.getNOofReceivedSignals(), is(equalTo(firstCell.getNOofReceivedSignals()))); + assertThat(firstUnmarshalledCell.getNOofMissingSignals(), is(equalTo(firstCell.getNOofMissingSignals()))); + assertThat(firstUnmarshalledCell.getNumberOfVsiMessages(), is(equalTo(firstCell.getNumberOfVsiMessages()))); + assertThat(firstUnmarshalledCell.getAverageSignalStrength(), is(equalTo(firstCell.getAverageSignalStrength()))); + + Map firstUnmarshalledCellFixedWidthSpans = firstUnmarshalledCell.getFixedWidthSpans(); + assertThat(firstUnmarshalledCellFixedWidthSpans.size(), is(equalTo(2))); + + for (Long timespanKey : firstUnmarshalledCellFixedWidthSpans.keySet()) { + TimeSpan expectedTimeSpan = firstCell.getFixedWidthSpans().get(timespanKey); + TimeSpan unmarshalledTimeSpan = firstUnmarshalledCellFixedWidthSpans.get(timespanKey); + + assertThat(unmarshalledTimeSpan.getFirstMessage(), is(equalTo(expectedTimeSpan.getFirstMessage()))); + assertThat(unmarshalledTimeSpan.getLastMessage(), is(equalTo(expectedTimeSpan.getLastMessage()))); + assertThat(unmarshalledTimeSpan.getMessageCounterSat(), is(equalTo(expectedTimeSpan.getMessageCounterSat()))); + assertThat(unmarshalledTimeSpan.getMessageCounterTerrestrial(), is(equalTo(expectedTimeSpan.getMessageCounterTerrestrial()))); + assertThat(unmarshalledTimeSpan.getMessageCounterTerrestrialUnfiltered(), is(equalTo(expectedTimeSpan.getMessageCounterTerrestrialUnfiltered()))); + assertThat(unmarshalledTimeSpan.getMissingSignals(), is(equalTo(expectedTimeSpan.getMissingSignals()))); + assertThat(unmarshalledTimeSpan.getVsiMessageCounter(), is(equalTo(expectedTimeSpan.getVsiMessageCounter()))); + assertThat(unmarshalledTimeSpan.getAverageSignalStrength(), is(equalTo(expectedTimeSpan.getAverageSignalStrength()))); + } + + Cell secondUnmarshalledCell = cellsFromDefaultSource.next(); + assertThat(secondUnmarshalledCell.getId(), is(equalTo(secondCell.getId()))); + assertThat(secondUnmarshalledCell.getLatitude(), is(closeTo(secondCell.getLatitude(), DELTA_WHEN_COMPARING_DOUBLE))); + assertThat(secondUnmarshalledCell.getLongitude(), is(closeTo(secondCell.getLongitude(), DELTA_WHEN_COMPARING_DOUBLE))); + assertThat(secondUnmarshalledCell.getNOofReceivedSignals(), is(equalTo(secondCell.getNOofReceivedSignals()))); + assertThat(secondUnmarshalledCell.getNOofMissingSignals(), is(equalTo(secondCell.getNOofMissingSignals()))); + assertThat(secondUnmarshalledCell.getNumberOfVsiMessages(), is(equalTo(secondCell.getNumberOfVsiMessages()))); + assertThat(secondUnmarshalledCell.getAverageSignalStrength(), is(equalTo(secondCell.getAverageSignalStrength()))); + + assertThat(secondUnmarshalledCell.getFixedWidthSpans().isEmpty(), is(true)); + } +} diff --git a/src/test/java/dk/dma/ais/coverage/persistence/MongoDatabaseInstanceTest.java b/src/test/java/dk/dma/ais/coverage/persistence/MongoDatabaseInstanceTest.java new file mode 100644 index 0000000..1431fd2 --- /dev/null +++ b/src/test/java/dk/dma/ais/coverage/persistence/MongoDatabaseInstanceTest.java @@ -0,0 +1,225 @@ +package dk.dma.ais.coverage.persistence; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.hasItem; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; + +import java.net.InetSocketAddress; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.bson.Document; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import com.mongodb.MongoClient; +import com.mongodb.MongoClientOptions; +import com.mongodb.ServerAddress; +import com.mongodb.client.MongoIterable; +import de.bwaldvogel.mongo.MongoServer; +import de.bwaldvogel.mongo.backend.memory.MemoryBackend; +import dk.dma.ais.coverage.Helper; +import dk.dma.ais.coverage.configuration.AisCoverageConfiguration; +import dk.dma.ais.coverage.configuration.DatabaseConfiguration; +import dk.dma.ais.coverage.data.Cell; +import dk.dma.ais.coverage.fixture.CellFixture; + +public class MongoDatabaseInstanceTest { + private static final String NORDIC_COVERAGE_DATABASE = "nordicCoverage"; + private static final String COVERAGE_DATA_COLLECTION = "coverageData"; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private MongoDatabaseInstance databaseInstance; + private CoverageDataMarshaller marshaller; + private MongoServer mongoServer; + private DatabaseConfiguration configuration; + private InetSocketAddress serverAddress; + private MongoClient mongoClient; + + @Before + public void setUp() throws Exception { + marshaller = mock(CoverageDataMarshaller.class); + databaseInstance = new MongoDatabaseInstance(marshaller); + Helper.conf = new AisCoverageConfiguration(); + } + + @After + public void tearDown() throws Exception { + databaseInstance.close(); + closeMongoClient(); + shutdownMongoServer(); + Helper.conf = null; + } + + private void closeMongoClient() { + if (mongoClient != null) { + mongoClient.close(); + } + } + + private void shutdownMongoServer() { + if (mongoServer != null) { + mongoServer.shutdown(); + } + } + + @Test + public void givenNoDatabase_whenCreateDatabase_thenDatabaseIsCreated() { + startMongoServer(); + useStartedMongoServer(); + + databaseInstance.createDatabase(); + + initializeMongoClient(); + MongoIterable existingDatabases = mongoClient.listDatabaseNames(); + assertThat(existingDatabases, hasItem(NORDIC_COVERAGE_DATABASE)); + assertThat(mongoClient.getDatabase(NORDIC_COVERAGE_DATABASE), is(notNullValue())); + } + + + private void startMongoServer() { + mongoServer = new MongoServer(new MemoryBackend()); + serverAddress = mongoServer.bind(); + } + + private void useStartedMongoServer() { + configuration = new DatabaseConfiguration(); + configuration.setType("MongoDB"); + configuration.setDbName(NORDIC_COVERAGE_DATABASE); + configuration.setAddr(serverAddress.getHostName()); + configuration.setPort(serverAddress.getPort()); + + databaseInstance.open(configuration); + } + + private void initializeMongoClient() { + MongoClientOptions options = MongoClientOptions.builder().serverSelectionTimeout(300).build(); + mongoClient = new MongoClient(new ServerAddress(serverAddress), options); + } + + @Test + public void givenAnExistingDatabase_whenCreateDatabase_thenNoExceptionIsThrown() { + startMongoServer(); + useStartedMongoServer(); + preCreateCollection(); + + databaseInstance.createDatabase(); + } + + private void preCreateCollection() { + initializeMongoClient(); + mongoClient.getDatabase(NORDIC_COVERAGE_DATABASE).createCollection(COVERAGE_DATA_COLLECTION); + } + + @Test + public void givenATimeOut_whenCreateDatabase_thenDatabaseConnectionExceptionIsThrown() { + testThatInvalidConfigurationThrowsDatabaseConnectionException("10.1.1.1", 16256); + } + + private void testThatInvalidConfigurationThrowsDatabaseConnectionException(String host, int port) { + thrown.expect(DatabaseConnectionException.class); + thrown.expectCause(is(notNullValue(Throwable.class))); + thrown.expectMessage(containsString(NORDIC_COVERAGE_DATABASE)); + thrown.expectMessage(containsString(String.format("%s:%d", host, port))); + + DatabaseConfiguration invalidConfiguration = new DatabaseConfiguration(); + invalidConfiguration.setType("MongoDB"); + invalidConfiguration.setDbName(NORDIC_COVERAGE_DATABASE); + invalidConfiguration.setAddr(host); + invalidConfiguration.setPort(16256); + + databaseInstance.setMongoClientOptions(MongoClientOptions.builder().serverSelectionTimeout(10).build()); + + databaseInstance.open(invalidConfiguration); + databaseInstance.createDatabase(); + } + + @Test + public void givenAnUnknownHost_whenCreateDatabase_thenDatabaseConnectionExceptionIsThrown() { + testThatInvalidConfigurationThrowsDatabaseConnectionException("unknownHost", 16256); + } + + @Test + public void givenACell_whenSave_thenCellIsPersistedInCoverageDataCollection() { + startMongoServer(); + useStartedMongoServer(); + preCreateCollection(); + databaseInstance.setMarshaller(new MongoCoverageDataMarshaller()); + + Cell aCell = CellFixture.createCellWithTimeSpans(); + + PersistenceResult result = databaseInstance.save(Collections.singletonMap("default", Arrays.asList(aCell))); + + assertThat(result.getStatus(), is(equalTo(PersistenceResult.Status.SUCCESS))); + assertThat(result.getWrittenCells(), is(1L)); + assertThat(mongoClient.getDatabase(NORDIC_COVERAGE_DATABASE).getCollection(COVERAGE_DATA_COLLECTION).count(), is(1L)); + } + + @Test + public void givenDatabaseInstanceIsNotOpened_whenCreateDatabase_thenIllegalStateExceptionIsThrown() { + thrown.expect(IllegalStateException.class); + + databaseInstance.createDatabase(); + } + + @Test + public void givenDatabaseInstanceIsNotOpened_whenSave_thenIllegalStateExceptionIsThrown() { + thrown.expect(IllegalStateException.class); + + databaseInstance.save(Collections.emptyMap()); + } + + @Test + public void givenDatabaseInstanceIsNotOpened_whenLoadLatestSavedCoverageData_thenIllegalStateExceptionIsThrown() { + thrown.expect(IllegalStateException.class); + + databaseInstance.loadLatestSavedCoverageData(); + } + + @Test + public void givenNoSavedCoverageData_whenLoadLatestSavedCoverageData_thenEmptyListIsReturned() { + startMongoServer(); + useStartedMongoServer(); + preCreateCollection(); + + Map> cells = databaseInstance.loadLatestSavedCoverageData(); + + assertThat(cells.isEmpty(), is(true)); + } + + @Test + public void givenSavedCoverageData_whenLoadLatestSavedCoverageData_thenThisDataIsReturned() { + startMongoServer(); + useStartedMongoServer(); + preCreateCollection(); + + Cell aCell = CellFixture.createCellWithTimeSpans(); + Cell anotherCell = CellFixture.createCellWithNoTimeSpan(); + List cells = Arrays.asList(aCell, anotherCell); + MongoCoverageDataMarshaller marshaller = new MongoCoverageDataMarshaller(); + Document document = marshaller.marshall(Collections.singletonMap("default", cells), ZonedDateTime.now(ZoneId.of("UTC"))); + + mongoClient.getDatabase(NORDIC_COVERAGE_DATABASE).getCollection(COVERAGE_DATA_COLLECTION).insertOne(document); + + databaseInstance.setMarshaller(marshaller); + + Map> loadedCoverageData = databaseInstance.loadLatestSavedCoverageData(); + + assertThat(loadedCoverageData.size(), is(equalTo(1))); + assertThat(loadedCoverageData.get("default").size(), is(equalTo(2))); + } +} diff --git a/src/test/java/dk/dma/ais/coverage/persistence/PersistenceResultTest.java b/src/test/java/dk/dma/ais/coverage/persistence/PersistenceResultTest.java new file mode 100644 index 0000000..bec7e8c --- /dev/null +++ b/src/test/java/dk/dma/ais/coverage/persistence/PersistenceResultTest.java @@ -0,0 +1,26 @@ +package dk.dma.ais.coverage.persistence; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +public class PersistenceResultTest { + + @Test + public void whenSuccess_thenResultStatusIsSuccessAndWrittenNumberOfCellsIsAsProvided() { + PersistenceResult result = PersistenceResult.success(3L); + + assertThat(result.getStatus(), is(equalTo(PersistenceResult.Status.SUCCESS))); + assertThat(result.getWrittenCells(), is(3L)); + } + + @Test + public void whenFailure_thenResultStatusIsFailureAndWrittenNumberOfCellsIsZero() { + PersistenceResult result = PersistenceResult.failure(); + + assertThat(result.getStatus(), is(equalTo(PersistenceResult.Status.FAILURE))); + assertThat(result.getWrittenCells(), is(0L)); + } +} diff --git a/src/test/java/dk/dma/ais/coverage/persistence/PersisterServiceTest.java b/src/test/java/dk/dma/ais/coverage/persistence/PersisterServiceTest.java new file mode 100644 index 0000000..63aa5ce --- /dev/null +++ b/src/test/java/dk/dma/ais/coverage/persistence/PersisterServiceTest.java @@ -0,0 +1,134 @@ +package dk.dma.ais.coverage.persistence; + +import dk.dma.ais.coverage.data.ICoverageData; +import org.apache.commons.lang3.NotImplementedException; +import org.junit.Test; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.*; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.Mockito.*; + +public class PersisterServiceTest { + + @Test + public void whenStart_thenSaveIsInvokedOnDatabaseInstanceAtInterval() throws InterruptedException { + DatabaseInstance databaseInstance = mock(DatabaseInstance.class); + when(databaseInstance.save(anyMap())).thenReturn(PersistenceResult.success(1)); + ICoverageData coverageData = mock(ICoverageData.class); + PersisterService persisterService = new PersisterService(databaseInstance, coverageData); + ScheduledExecutorService executor = new RunImmediatelyFiveTimesExecutorServive(); + persisterService.setExecutor(executor); + + persisterService.start(); + persisterService.stop(); + + verify(databaseInstance, times(5)).save(anyMap()); + } + + @Test + public void whenNewInstance_thenIntervalInMinutesDefaultsTo60() { + DatabaseInstance databaseInstance = mock(DatabaseInstance.class); + ICoverageData coverageData = mock(ICoverageData.class); + PersisterService persisterService = new PersisterService(databaseInstance, coverageData); + + assertThat(persisterService.getIntervalInMinutes(), is(equalTo(60L))); + } + + private static class RunImmediatelyFiveTimesExecutorServive implements ScheduledExecutorService { + private static final String NOT_IMPLEMENTED = "Not implemented"; + + @Override + public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { + throw new NotImplementedException(NOT_IMPLEMENTED); + } + + @Override + public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { + throw new NotImplementedException(NOT_IMPLEMENTED); + } + + @Override + public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { + for (int i = 0; i < 5; i++) { + command.run(); + } + return null; + } + + @Override + public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { + throw new NotImplementedException(NOT_IMPLEMENTED); + } + + @Override + public void shutdown() { + + } + + @Override + public List shutdownNow() { + return null; + } + + @Override + public boolean isShutdown() { + return false; + } + + @Override + public boolean isTerminated() { + return false; + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + return false; + } + + @Override + public Future submit(Callable task) { + throw new NotImplementedException(NOT_IMPLEMENTED); + } + + @Override + public Future submit(Runnable task, T result) { + throw new NotImplementedException(NOT_IMPLEMENTED); + } + + @Override + public Future submit(Runnable task) { + throw new NotImplementedException(NOT_IMPLEMENTED); + } + + @Override + public List> invokeAll(Collection> tasks) throws InterruptedException { + throw new NotImplementedException(NOT_IMPLEMENTED); + } + + @Override + public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException { + throw new NotImplementedException(NOT_IMPLEMENTED); + } + + @Override + public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException { + throw new NotImplementedException(NOT_IMPLEMENTED); + } + + @Override + public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + throw new NotImplementedException(NOT_IMPLEMENTED); + } + + @Override + public void execute(Runnable command) { + throw new NotImplementedException(NOT_IMPLEMENTED); + } + } +} diff --git a/src/test/java/dk/dma/ais/coverage/persistence/TypeBasedDatabaseInstanceFactoryTest.java b/src/test/java/dk/dma/ais/coverage/persistence/TypeBasedDatabaseInstanceFactoryTest.java new file mode 100644 index 0000000..4a63022 --- /dev/null +++ b/src/test/java/dk/dma/ais/coverage/persistence/TypeBasedDatabaseInstanceFactoryTest.java @@ -0,0 +1,66 @@ +package dk.dma.ais.coverage.persistence; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class TypeBasedDatabaseInstanceFactoryTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private DatabaseInstanceFactory factory; + + @Before + public void setUp() throws Exception { + factory = new TypeBasedDatabaseInstanceFactory(); + } + + @Test + public void givenMemoryOnlyDatabaseType_whenCreateDatabaseInstance_thenInstanceReturnedIsOfTypeMemoryOnly() { + DatabaseInstance databaseInstance = factory.createDatabaseInstance("MemoryOnly"); + + assertThat(databaseInstance, is(instanceOf(MemoryOnlyDatabaseInstance.class))); + } + + @Test + public void givenMemoryOnlyDatabaseType_whenCreateDatabaseInstance_thenTypeMatchingIsCaseInsensitive() { + testThatTypeMatchingIsCaseInsensitive("MemoryOnly", MemoryOnlyDatabaseInstance.class); + } + + private void testThatTypeMatchingIsCaseInsensitive(String databaseType, Class expectedInstanceType) { + DatabaseInstance camelCaseMemoryOnlyDatabaseInstance = factory.createDatabaseInstance(databaseType); + DatabaseInstance lowerCaseMemoryOnlyDatabaseInstance = factory.createDatabaseInstance(databaseType.toLowerCase()); + DatabaseInstance upperCaseMemoryOnlyDatabaseInstance = factory.createDatabaseInstance(databaseType.toUpperCase()); + + assertThat(camelCaseMemoryOnlyDatabaseInstance, is(instanceOf(expectedInstanceType))); + assertThat(lowerCaseMemoryOnlyDatabaseInstance, is(instanceOf(expectedInstanceType))); + assertThat(upperCaseMemoryOnlyDatabaseInstance, is(instanceOf(expectedInstanceType))); + } + + @Test + public void givenMongoDbDatabaseType_whenCreateDatabaseInstance_thenInstanceReturnedIsOfTypeMongoDb() { + DatabaseInstance databaseInstance = factory.createDatabaseInstance("MongoDB"); + + assertThat(databaseInstance, is(instanceOf(MongoDatabaseInstance.class))); + } + + @Test + public void givenMongoDbDatabaseType_whenCreateDatabaseInstance_thenTypeMatchingIsCaseInsensitive() { + testThatTypeMatchingIsCaseInsensitive("MongoDB", MongoDatabaseInstance.class); + } + + @Test + public void givenUnknownDatabaseType_whenCreateDatabaseInstance_thenUnknownDatabaseTypeExceptionIsThrown() { + thrown.expect(UnknownDatabaseTypeException.class); + thrown.expectMessage(containsString("MySQL")); + + factory.createDatabaseInstance("MySQL"); + } +} diff --git a/src/test/java/dk/dma/ais/coverage/rest/CoverageRestServiceTest.java b/src/test/java/dk/dma/ais/coverage/rest/CoverageRestServiceTest.java new file mode 100644 index 0000000..5cf0673 --- /dev/null +++ b/src/test/java/dk/dma/ais/coverage/rest/CoverageRestServiceTest.java @@ -0,0 +1,86 @@ +package dk.dma.ais.coverage.rest; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.Date; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import dk.dma.ais.coverage.AisCoverageBuilder; +import dk.dma.ais.coverage.Helper; +import dk.dma.ais.coverage.export.data.Status; + +/** + * Tests in this class are synchronized because they are all using {@code Helper.firstMessage} and + * {@code Helper.latestMessage}, which are static members. + */ +public class CoverageRestServiceTest { + private AisCoverageBuilder aisCoverageBuilder; + private CoverageRestService restService; + + @Before + public void setUp() throws Exception { + aisCoverageBuilder = new AisCoverageBuilder(); + aisCoverageBuilder.withMockAisCoverageConfiguration() + .withMockAisBusConfiguration() + .withMockDatabaseConfiguration() + .withMockDatabaseInstanceForAnyType() + .build(); + + restService = new CoverageRestService(); + } + + @After + public void tearDown() throws Exception { + Helper.firstMessage = null; + Helper.latestMessage = null; + } + + @Test + public synchronized void givenNoMessageReceivedYet_whenStatus_thenStatusRunningIsReturned() throws IOException { + Date now = Helper.getFloorDate(new Date()); + + Status status = (Status) restService.status(); + + assertThatStatusIsRunning(status); + assertThat(status.firstMessage, is(equalTo(now.getTime()))); + assertThat(status.lastMessage, is(equalTo(now.getTime()))); + } + + private void assertThatStatusIsRunning(Status status) { + assertThat(status, is(not(nullValue()))); + assertThat(status.analysisStatus, is(equalTo("Running"))); + } + + @Test + public synchronized void givenAFewMessagesReceived_whenStatus_thenStatusRunningIsReturned_andFirstMessageTimestampIsSet_andLatestMessageTimestampIsSet() throws IOException { + Helper.firstMessage = Helper.getFloorDate(new Date()); + Helper.latestMessage = Helper.getCeilDate(new Date()); + + Status status = (Status) restService.status(); + + assertThatStatusIsRunning(status); + assertThat(status.firstMessage, is(equalTo(Helper.firstMessage.getTime()))); + assertThat(status.lastMessage, is(equalTo(Helper.latestMessage.getTime()))); + } + + @Test + public synchronized void givenNoTerrestrialMessageReceivedYet_whenStatus_thenStatusRunningIsReturned_andFirstMessageTimestampIsSet() throws IOException { + Helper.firstMessage = Helper.getFloorDate(new Date()); + Date now = Helper.getFloorDate(new Date()); + + Status status = (Status) restService.status(); + + assertThatStatusIsRunning(status); + assertThat(status.firstMessage, is(equalTo(Helper.firstMessage.getTime()))); + assertTrue(status.lastMessage >= now.getTime()); + } +} diff --git a/web/css/style.css b/web/css/style.css index 7b93ef3..1a0bf71 100644 --- a/web/css/style.css +++ b/web/css/style.css @@ -77,7 +77,7 @@ body { } #leftSide { - width: 230px; + width: 250px; padding: 0px; vertical-align: middle; position: absolute; @@ -228,8 +228,8 @@ hr { .colorbox { margin-left: 3px; float: left; - width: 20px; - height: 20px; + width: 15px; + height: 15px; } #redbox { @@ -287,4 +287,4 @@ hr { .grayPointer { background-image: url('../img/graypointer.png'); background-repeat: no-repeat; -} \ No newline at end of file +} diff --git a/web/index.html b/web/index.html index cfc642b..8cdb2fa 100644 --- a/web/index.html +++ b/web/index.html @@ -31,7 +31,7 @@ var y=document.getElementById("expotype").options; exportType.value = y[x].text; } - + @@ -40,12 +40,6 @@ - - - - - -
@@ -58,9 +52,19 @@
+ style="position: absolute; bottom: 15px; right: 10px; background: white;">
diff --git a/web/js/Copy of CoverageUI.js b/web/js/Copy of CoverageUI.js deleted file mode 100644 index e2e5fbb..0000000 --- a/web/js/Copy of CoverageUI.js +++ /dev/null @@ -1,506 +0,0 @@ -function CoverageUI () { - var self = this; - this.loading = false; - this.sources = []; - this.selectedSource = null; - this.changed = true; - this.minThreshold = 50; - this.maxThreshold = 80; - this.minExpectedMessages = 100; - this.exportMultiplicationFactor = 4; - - - - this.setupUI = function(){ - - // Set zoom panel positon - $(".olControlZoom").css('left', zoomPanelPositionLeft); - $(".olControlZoom").css('top', zoomPanelPositionTop); - - // Set loading panel positon - var x = $(document).width() / 2 - $("#loadingPanel").width() / 2; - $("#loadingPanel").css('left', x); - - // Update mouse location when moved - map.events.register("mousemove", map, function(e) { - var position = this.events.getMousePosition(e); - pixel = new OpenLayers.Pixel(position.x, position.y); - var lonLat = map.getLonLatFromPixel(pixel).transform( - map.getProjectionObject(), // from Spherical Mercator Projection - new OpenLayers.Projection("EPSG:4326") // to WGS 1984 - ); - $("#location").html(lonLat.lon.toFixed(4) + ", " + lonLat.lat.toFixed(4)); - }); - - // Init expandable panels - $('#thresholdPanel').expandable({ - header: "Coverage Settings" - }); - $('#featureDetailsPanel').expandable({ - header: "Source Details" - }); - $('#sourcesPanel').expandable({ - header: 'Sources
Select all' - }); - $('#exportPanel').expandable({ - header: "Export" - }); - //add check box listeners - $(document).on('change', ".sourceCheckbox", function(e) { - self.sources[this.id].enabled=$(this).is(':checked'); - self.refreshSourceList(); - self.refreshSourceDetails(); - self.changed=true; - }); - - $(document).on('change', "#selectall", function(e) { - var element = $(this); - $.each(self.sources, function(key, source) { - source.enabled=element.is(':checked'); - }); - self.refreshSourceList(); - self.refreshSourceDetails(); - self.changed=true; - }); - - - //setup the threshold slider - $( "#slider-range" ).slider({ - range: true, - min: 0, - max: 100, - values: [ self.minThreshold, self.maxThreshold ], - slide: function( event, ui ) { - self.minThreshold = ui.values[ 0 ]; - self.maxThreshold = ui.values[ 1 ]; - $( "#min-range" ).html( " < " + self.minThreshold + "% <= " ); - $( "#max-range" ).html( " < " + self.maxThreshold + "% <= " ); - self.changed = true; - } - }); - $( "#min-range" ).html( " < " + self.minThreshold + "% <= " ); - $( "#max-range" ).html( " < " + self.maxThreshold + "% <= " ); - - //setup min expected messages per cell slider - var minExpected = $("#minExpected"); - minExpected.html(self.minExpectedMessages); - $( "#filterSlider" ).slider({ - min: 0, - max: 1000, - value: self.minExpectedMessages, - slide: function(event, ui){ - self.minExpectedMessages=ui.value; - minExpected.html(ui.value); - self.changed = true; - } - }); - - //setup - var exportMultiplicationDivHidden = $("#exportMultiHidden"); - var exportMultiplicationDiv = $("#exportMultiplicationFactor"); - exportMultiplicationDivHidden.val(self.exportMultiplicationFactor); - exportMultiplicationDiv.html(self.exportMultiplicationFactor); - $( "#multiplicationSlider" ).slider({ - min: 1, - max: 60, - value: self.exportMultiplicationFactor, - slide: function(event, ui){ - self.minExpectedMessages=ui.value; - exportMultiplicationDiv.html(ui.value); - exportMultiplicationDivHidden.val(ui.value); - } - }); - - - //setting the loop function - var myTimeout; - function loopFunction () {; - if(self.changed){ - self.drawCoverage(); - self.changed = false; - } - myTimeout = setTimeout(loopFunction, 2000); - } - myTimeout = setTimeout(loopFunction, 2000); - - //map changed listener - map.events.register('moveend', this, function (event) { - - - - //update export div - self.exportMultiplicationFactor = self.getMultiplicationFactor(); - exportMultiplicationDivHidden.val(self.exportMultiplicationFactor); - exportMultiplicationDiv.html(self.exportMultiplicationFactor); - $( "#multiplicationSlider" ).slider({value:self.getMultiplicationFactor()}); - - - //draw coverage - coverageUI.drawCoverage(); - - }); - } - - - - /** - * Adds all the layers that will contain graphic. - */ - this.addLayers = function(){ - - // Get renderer - var renderer = OpenLayers.Util.getParameters(window.location.href).renderer; - renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers; - // renderer = ["Canvas", "SVG", "VML"]; - - // Add OpenStreetMap Layer - var osm = new OpenLayers.Layer.OSM( - "OSM", - "http://a.tile.openstreetmap.org/${z}/${x}/${y}.png", - { - 'layers':'basic', - 'isBaseLayer': true - } - ); - map.addLayer(osm); - - //polygon layer - polygonLayer = new OpenLayers.Layer.Vector("Polygon Layer",{ - styleMap: new OpenLayers.StyleMap({ - "default": new OpenLayers.Style({ - strokeColor: "black", - strokeOpacity: .7, - strokeWidth: 1, - fillColor: "${fillcolor}", - fillOpacity: .6, - cursor: "pointer" - }), - "select": new OpenLayers.Style({ - strokeColor: "blue", - strokeOpacity: .7, - strokeWidth: 4, - }) - }) - }); -// polygonLayer.styleMap.styles['default'].defaultStyle={fillColor: 'blue'} - map.addLayers([polygonLayer]); - - //source layer - sourceLayer = new OpenLayers.Layer.Vector("Sources", { - styleMap: new OpenLayers.StyleMap({ - "default": new OpenLayers.Style({ - strokeColor: "black", - strokeOpacity: .7, - strokeWidth: 1, - fillColor: "${fillcolor}", - fillOpacity: .6, - cursor: "pointer" - }), - "select": new OpenLayers.Style({ - strokeColor: "blue", - strokeOpacity: .7, - strokeWidth: 4, - }) - }) - }); - map.addLayer(sourceLayer); - - //select control for source layer - selectControl = new OpenLayers.Control.SelectFeature([sourceLayer, polygonLayer], - { - clickout: true, - hover: false, - highlightOnly: false, - eventListeners: { - featurehighlighted: self.onFeatureSelect, - featureunhighlighted: self.onFeatureUnselect - } - - } - ); - selectControl.handlers.feature.stopDown = false; - map.addControl(selectControl); - selectControl.activate(); - } - - this.refreshSourceList = function(){ - var sourceContainer = $("#sourcesPanel"); - sourceContainer.html(""); - var sourceshtml = ""; - $.each(self.sources, function(key, source) { - var checked = ""; - if(source.enabled){ - checked = 'checked="checked"'; - } - sourceshtml += '
'+source.mmsi+'
'+source.type+'
'; - }); - sourceContainer.html(sourceshtml); - self.drawSources(); - } - - this.refreshSourceDetails = function(){ - - if(self.selectedSource == null){ - return; - } - var checked=""; - if(self.selectedSource.enabled){ - checked='checked="checked"'; - } - - $("#featureDetailsPanel").html('
Id
'+ - '
'+feature.mmsi+'
'+ - '
Type
'+ - '
'+feature.type+'
'+ - '
Lat
'+ - '
'+feature.lat.toFixed(4)+'
'+ - '
Lon
'+ - '
'+feature.lon.toFixed(4)+'
'+ - '
Enabled
'+ - '
'); - } - - this.drawSources = function(){ - sourceLayer.removeAllFeatures(); - var drawTheSource = this.drawSource - $.each(this.sources, function(key, val) { - drawTheSource(val); - }); - } - - this.drawSource = function(val){ - var image; - if(val.enabled){ - image = 'img/marker.png'; - }else{ - image = 'img/marker2.png' - } - var feature = new OpenLayers.Feature.Vector( - new OpenLayers.Geometry.Point( val.lon , val.lat ).transform( - new OpenLayers.Projection("EPSG:4326"), // transform from WGS 1984 - map.getProjectionObject() // to Spherical Mercator Projection - ), - {hmm:'100'}); -// {externalGraphic: image, graphicHeight: 21, graphicWidth: 16, cursor: "crosshair", fillColor: "#ffcc66", pointRadius: "10"}); - feature.mmsi = val.mmsi; - feature.type = val.type; - feature.lat = val.lat; - feature.lon = val.lon; - feature.source = val; - val.feature=feature; - - if(val.enabled){ - feature.style = { - pointRadius: "10", // sized according to type attribute - fillColor: "#980000 ", - strokeColor: "black", - strokeWidth: 2, - graphicZIndex: 1, - cursor: "crosshair" - } - }else{ - feature.style = { - pointRadius: "10", // sized according to type attribute - fillColor: "white", - strokeColor: "black", - strokeWidth: 2, - graphicZIndex: 1, - cursor: "crosshair" - } - } - if(self.selectedSource == val){ - feature.style["strokeColor"] = "blue"; - }else{ - feature.style["strokeColor"] = "black"; - } - - sourceLayer.addFeatures(feature); - } - - - this.drawCoverage = function(){ - - //get the multiplication factor for corresponding zoom level - var multifactor = self.getMultiplicationFactor(); -// multifactor = 5; - $('#multiplicationFactor').html(multifactor); - - // activate loading panel - $("#loadingPanel").css('visibility', 'visible'); - - // get lat lon points for each screen corner - var topleftpixel = new OpenLayers.Pixel(0, 0); - var bottomrightpixel = new OpenLayers.Pixel($("#map").width(), $("#map").height()); - var topleftlonlat = map.getLonLatFromPixel(topleftpixel).transform( - map.getProjectionObject(), // from Spherical Mercator Projection - new OpenLayers.Projection("EPSG:4326") // to WGS 1984 - ); - var bottomrightlonlat = map.getLonLatFromPixel(bottomrightpixel).transform( - map.getProjectionObject(), // from Spherical Mercator Projection - new OpenLayers.Projection("EPSG:4326") // to WGS 1984 - ); - var screenarea = topleftlonlat.lat.toFixed(4)+","+topleftlonlat.lon.toFixed(4)+","+bottomrightlonlat.lat.toFixed(4)+","+bottomrightlonlat.lon.toFixed(4); - - //convert enabled sources to string to be sent - var dataToBeSent = self.enabledSourcesToString(); - - polygonLayer.removeAllFeatures(); - - //use json client to fetch data from web service - aisJsonClient.getCoverage(dataToBeSent, screenarea, multifactor, function(data){ - - $('#latSize').html(data.latSize.toFixed(4)); - $('#lonSize').html(data.lonSize.toFixed(4)); - - var minExpectedMessages = - $.each(data.cells, function(key, val) { - var points = [ - new OpenLayers.Geometry.Point(val.lon, val.lat), - new OpenLayers.Geometry.Point(val.lon, val.lat+data.latSize), - new OpenLayers.Geometry.Point(val.lon+data.lonSize, val.lat+data.latSize), - new OpenLayers.Geometry.Point(val.lon+data.lonSize, val.lat) - ]; - var expectedMessages = (val.nrOfMisMes+val.nrOfRecMes); - var coverageValue = val.nrOfRecMes/expectedMessages; - var color; - if(expectedMessages >= self.minExpectedMessages){ - if(coverageValue >= self.maxThreshold/100){ - color ='green'; - }else if(coverageValue >= self.minThreshold/100){ - color ='yellow'; - }else{ - color ='red'; - } - - self.drawPolygon({ - lat: val.lat, - lon: val.lon, - points: points, - fillcolor: color, - totalMessages: (val.nrOfMisMes+val.nrOfRecMes), - receivedMessages: val.nrOfRecMes - }); - } - }); -// self.loading = false; - $("#loadingPanel").css('visibility', 'hidden'); - }); - } - - this.drawPolygon = function(options){ - var site_points = []; - for (i in options.points) { - options.points[i].transform( - new OpenLayers.Projection("EPSG:4326"), // transform from WGS 1984 - map.getProjectionObject() // to Spherical Mercator Projection - ); - site_points.push(options.points[i]); - } - var linear_ring = new OpenLayers.Geometry.LinearRing(site_points); - polygonFeature = new OpenLayers.Feature.Vector( - new OpenLayers.Geometry.Polygon([linear_ring]),null); - polygonFeature.attributes.fillcolor = options.fillcolor; - polygonFeature.type = "cell"; - polygonFeature.lon = options.lon; - polygonFeature.lat = options.lat; - polygonFeature.totalMessages = options.totalMessages; - polygonFeature.receivedMessages = options.receivedMessages; - polygonLayer.addFeatures([polygonFeature]); - - } - - this.enabledSourcesToString = function(){ - var string=""; - $.each(this.sources, function(key, source) { - if(source.enabled){ - string += source.mmsi+","; - } - }); - return string; - } - - this.getMultiplicationFactor = function(){ - var zLevel = map.getZoom(); - var multifactor; - if(zLevel > 10){ - multifactor = 1; - }else if(zLevel == 10){ - multifactor = 1; - }else if(zLevel == 9){ - multifactor = 2; - }else if(zLevel == 8){ - multifactor = 3; - }else if(zLevel == 7){ - multifactor = 4; - }else if(zLevel == 6){ - multifactor = 8; - }else if(zLevel == 5){ - multifactor = 20; - }else if(zLevel == 4){ - multifactor = 40; - }else if(zLevel == 3){ - multifactor = 60; - }else if(zLevel == 2){ - multifactor = 60; - }else if(zLevel == 1){ - multifactor = 80; - } -// alert(multifactor) - return multifactor; - } - - this.onFeatureSelect = function(evt) { - - feature = evt.feature; - - //determine if feature is a cell or a source - if(feature.type == "cell"){ - - //remove potential source - self.selectedSource = null; - self.drawSources(); - - //Setting up cell details panel - $("#featureDetailsPanel > .panelHeader").html("Cell Details"); - $("#featureDetailsPanel").html('
Source
'+ - '
'+feature.mmsi+'
'+ - '
Cell Latitude
'+ - '
'+feature.lat.toFixed(4)+'
'+ - '
Cell Longitude
'+ - '
'+feature.lon.toFixed(4)+'
'+ - '
Received Messages
'+ - '
'+feature.receivedMessages+'
'+ - '
Expected Messages
'+ - '
'+feature.totalMessages+'
'+ - '
Coverage
'+ - '
'+((feature.receivedMessages/feature.totalMessages)*100).toFixed(2)+' %
'); - $("#featureDetailsPanel").slideDown(); - - - }else{ - self.selectedSource = self.sources[feature.mmsi]; - self.refreshSourceDetails(); - $("#featureDetailsPanel").slideDown(); - $("#featureDetailsPanel > .panelHeader").html("Source Details"); - self.drawSources(); - } - - } - - this.onFeatureUnselect = function(evt) { - -// feature = evt.feature; -// -// //determine if feature is a cell or a source -// if(feature.type == "cell"){ -// -// } -//// else{ -// self.selectedSource = null; -//// $("#featureDetailsPanel").slideUp(); -// self.drawSources(); -//// } - - } - -} \ No newline at end of file diff --git a/web/js/CoverageUI.js b/web/js/CoverageUI.js index cffd2bd..0dfb88c 100644 --- a/web/js/CoverageUI.js +++ b/web/js/CoverageUI.js @@ -1,61 +1,67 @@ -function CoverageUI () { - var self = this; - this.loading = false; +function CoverageUI() { + var self = this; + this.loading = false; this.sources = []; this.selectedSource = null; this.changed = true; - this.minThreshold = 50; - this.maxThreshold = 80; + this.minThreshold = 20; + this.maxThreshold = 50; + this.savedMinThreshold = -107; + this.savedMaxThreshold = -101; + this.thresholdUnit = "%" + this.savedThresholdUnit = 'dBm'; this.minExpectedMessages = 100; this.exportMultiplicationFactor = 4; this.startDate; //is the time when the analysis started (used for sliding window) this.endDate; //Is the end time of the analysis or the point where the analysis is now (used for sliding window) this.selectedStartDate; this.selectedEndDate; + this.exportDataType = 'received_messages'; + this.savedExportDataType = 'signal_strength'; + var topleftPoint;//points which define sat box var bottomRightPoint - var shiptrackactive=false; - var satChartMethod='satonly'; - - - - this.setupUI = function(){ - - // Set zoom panel positon - $(".olControlZoom").css('left', zoomPanelPositionLeft); - $(".olControlZoom").css('top', zoomPanelPositionTop); - - // Set loading panel positon - var x = $(document).width() / 2 - $("#loadingPanel").width() / 2; - $("#loadingPanel").css('left', x); - - // Update mouse location when moved - map.events.register("mousemove", map, function(e) { - var position = this.events.getMousePosition(e); - pixel = new OpenLayers.Pixel(position.x, position.y); - var lonLat = map.getLonLatFromPixel(pixel).transform( - map.getProjectionObject(), // from Spherical Mercator Projection - new OpenLayers.Projection("EPSG:4326") // to WGS 1984 - ); - $("#location").html(lonLat.lon.toFixed(4) + ", " + lonLat.lat.toFixed(4)); - }); - - // Init expandable panels - $('#thresholdPanel').expandable({ - header: "Coverage Settings" - }); - $('#featureDetailsPanel').expandable({ - header: "Source Details" - }); - $('#sourcesPanel').expandable({ - header: 'Sources
Select all' - }); - $('#exportPanel').expandable({ - header: "Export" - }); - $('#shiptrackPanel').expandable({ - header: "Ship Tracking" - }); + var shiptrackactive = false; + var satChartMethod = 'satonly'; + + + this.setupUI = function () { + + // Set zoom panel positon + $(".olControlZoom").css('left', zoomPanelPositionLeft); + $(".olControlZoom").css('top', zoomPanelPositionTop); + + // Set loading panel positon + var x = $(document).width() / 2 - $("#loadingPanel").width() / 2; + $("#loadingPanel").css('left', x); + + // Update mouse location when moved + map.events.register("mousemove", map, function (e) { + var position = this.events.getMousePosition(e); + pixel = new OpenLayers.Pixel(position.x, position.y); + var lonLat = map.getLonLatFromPixel(pixel).transform( + map.getProjectionObject(), // from Spherical Mercator Projection + new OpenLayers.Projection("EPSG:4326") // to WGS 1984 + ); + $("#location").html(lonLat.lon.toFixed(4) + ", " + lonLat.lat.toFixed(4)); + }); + + // Init expandable panels + $('#thresholdPanel').expandable({ + header: "Coverage Settings" + }); + $('#featureDetailsPanel').expandable({ + header: "Source Details" + }); + $('#sourcesPanel').expandable({ + header: 'Sources
Select all' + }); + $('#exportPanel').expandable({ + header: "Export" + }); + $('#shiptrackPanel').expandable({ + header: "Ship Tracking" + }); // $('#bottomPanel').expandable({ // header: "Satellite Statistics", // maxHeight: "800px" @@ -64,568 +70,600 @@ function CoverageUI () { // header: "Sliding Window", // maxHeight: "800px" // }); - - //add check box listeners - $(document).on('change', ".sourceCheckbox", function(e) { - self.sources[this.id].enabled=$(this).is(':checked'); - self.refreshSourceList(); - self.refreshSourceDetails(); - self.changed=true; - }); - - $(document).on('change', "#selectall", function(e) { - var element = $(this); - $.each(self.sources, function(key, source) { - source.enabled=element.is(':checked'); - }); - self.refreshSourceList(); - self.refreshSourceDetails(); - self.changed=true; - }); - - //Close window listeners - $('.close').parent().hide(); - $('.close').click(function(){ - $(this).parent().fadeOut(100); - //remove sat rectangle + + //add check box listeners + $(document).on('change', ".sourceCheckbox", function (e) { + self.sources[this.id].enabled = $(this).is(':checked'); + self.refreshSourceList(); + self.refreshSourceDetails(); + self.changed = true; + }); + + $(document).on('change', "#selectall", function (e) { + var element = $(this); + $.each(self.sources, function (key, source) { + source.enabled = element.is(':checked'); + }); + self.refreshSourceList(); + self.refreshSourceDetails(); + self.changed = true; + }); + + $(document).on('change', "#coverageType", function (e) { + var maxThresholdToSave = self.maxThreshold; + var minThresholdToSave = self.minThreshold; + var thresholdUnitToSave = self.thresholdUnit; + var exportDataTypeToSave = self.exportDataType; + + self.maxThreshold = self.savedMaxThreshold; + self.minThreshold = self.savedMinThreshold; + self.thresholdUnit = self.savedThresholdUnit; + self.exportDataType = self.savedExportDataType; + + self.savedMaxThreshold = maxThresholdToSave; + self.savedMinThreshold = minThresholdToSave; + self.savedThresholdUnit = thresholdUnitToSave; + self.savedExportDataType = exportDataTypeToSave; + + self.drawSlider(); + self.cleanupCellDetails(); + + $('#exportDataType').val(self.exportDataType); + + self.changed = true; + }); + + //Close window listeners + $('.close').parent().hide(); + $('.close').click(function () { + $(this).parent().fadeOut(100); + //remove sat rectangle // boxLayer.removeFeatures([boxLayer.features[0]]); - boxLayer.removeAllFeatures(); - - }); - - //Open ship tracking chart listener - $('#shiptrackingOpenChartBtn').click(function(){ + boxLayer.removeAllFeatures(); + + }); + + //Open ship tracking chart listener + $('#shiptrackingOpenChartBtn').click(function () { // alert("open bar chart"); - self.updateShipTrackBarChart(); - $('#trackingBarchartWindow').fadeIn(100); - - }); - - //Draw ship track listener - $('#shiptrackingMapBtn').click(function(){ - console.log("draw ship track"); + self.updateShipTrackBarChart(); + $('#trackingBarchartWindow').fadeIn(100); + + }); + + //Draw ship track listener + $('#shiptrackingMapBtn').click(function () { + console.log("draw ship track"); // self.drawLine(55,6,58,9); - self.updateShipTrack(); - - }); - - //Chart method change - listener - $( "#chartMethodSelect" ).change(function() { - satChartMethod=$( this ).val(); - self.updateBarChart(); - }); - - - - - //setup the threshold slider - $( "#slider-range" ).dragslider({ - range: true, - min: 0, - max: 100, - rangeDrag: true, - values: [ self.minThreshold, self.maxThreshold ], - slide: function( event, ui ) { - self.minThreshold = ui.values[ 0 ]; - self.maxThreshold = ui.values[ 1 ]; - $( "#min-range" ).html( " < " + self.minThreshold + "% <= " ); - $( "#max-range" ).html( " < " + self.maxThreshold + "% <= " ); - }, - change: function(event, ui){ - self.changed = true; - } - }); - $( "#min-range" ).html( " < " + self.minThreshold + "% <= " ); - $( "#max-range" ).html( " < " + self.maxThreshold + "% <= " ); - - //setup min expected messages per cell slider - var minExpected = $("#minExpected"); - minExpected.html(self.minExpectedMessages); - $( "#filterSlider" ).slider({ - min: 0, - max: 1000, - value: self.minExpectedMessages, - slide: function(event, ui){ - self.minExpectedMessages=ui.value; - minExpected.html(ui.value); - }, - change: function(event, ui){ - self.changed = true; - } - }); - - - //setup sliding window - self.updateSlidingWindow(); - - - //setup - var exportMultiplicationDivHidden = $("#exportMultiHidden"); - var exportMultiplicationDiv = $("#exportMultiplicationFactor"); - exportMultiplicationDivHidden.val(self.exportMultiplicationFactor); - exportMultiplicationDiv.html(self.exportMultiplicationFactor); - $( "#multiplicationSlider" ).slider({ - min: 1, - max: 60, - value: self.exportMultiplicationFactor, - slide: function(event, ui){ - self.minExpectedMessages=ui.value; - exportMultiplicationDiv.html(ui.value); - exportMultiplicationDivHidden.val(ui.value); - } - }); - - - //setting the loop function - var myTimeout; - function loopFunction () {; - if(self.changed){ - self.drawCoverage(); - //self.updateSlidingWindow(); - self.changed = false; - } - myTimeout = setTimeout(loopFunction, 2000); - } - myTimeout = setTimeout(loopFunction, 2000); - - //map changed listener - map.events.register('moveend', this, function (event) { - - - - //update export div - self.exportMultiplicationFactor = self.getMultiplicationFactor(); - exportMultiplicationDivHidden.val(self.exportMultiplicationFactor); - exportMultiplicationDiv.html(self.exportMultiplicationFactor); - $( "#multiplicationSlider" ).slider({value:self.getMultiplicationFactor()}); - - - //draw coverage - coverageUI.drawCoverage(); - //self.updateSlidingWindow(); - + self.updateShipTrack(); + + }); + + //Chart method change - listener + $("#chartMethodSelect").change(function () { + satChartMethod = $(this).val(); + self.updateBarChart(); + }); + + //setup the threshold slider + self.drawSlider(); + + //setup min expected messages per cell slider + var minExpected = $("#minExpected"); + minExpected.html(self.minExpectedMessages); + $("#filterSlider").slider({ + min: 0, + max: 1000, + value: self.minExpectedMessages, + slide: function (event, ui) { + self.minExpectedMessages = ui.value; + minExpected.html(ui.value); + }, + change: function (event, ui) { + self.changed = true; + } }); + + //setup sliding window + self.updateSlidingWindow(); + + //setup + var exportMultiplicationDivHidden = $("#exportMultiHidden"); + var exportMultiplicationDiv = $("#exportMultiplicationFactor"); + exportMultiplicationDivHidden.val(self.exportMultiplicationFactor); + exportMultiplicationDiv.html(self.exportMultiplicationFactor); + $("#multiplicationSlider").slider({ + min: 1, + max: 60, + value: self.exportMultiplicationFactor, + slide: function (event, ui) { + self.minExpectedMessages = ui.value; + exportMultiplicationDiv.html(ui.value); + exportMultiplicationDivHidden.val(ui.value); + } + }); + + //setting the loop function + var myTimeout; + + function loopFunction() { + ; + if (self.changed) { + self.drawCoverage(); + //self.updateSlidingWindow(); + self.changed = false; + } + myTimeout = setTimeout(loopFunction, 2000); + } + + myTimeout = setTimeout(loopFunction, 2000); + + //map changed listener + map.events.register('moveend', this, function (event) { + + //update export div + self.exportMultiplicationFactor = self.getMultiplicationFactor(); + exportMultiplicationDivHidden.val(self.exportMultiplicationFactor); + exportMultiplicationDiv.html(self.exportMultiplicationFactor); + $("#multiplicationSlider").slider({value: self.getMultiplicationFactor()}); + + + //draw coverage + coverageUI.drawCoverage(); + //self.updateSlidingWindow(); + + }); + } + + this.isVDMCategory = function () { + return $('#coverageType').val() == "VDM"; + } + + this.drawSlider = function () { + $("#slider-range").dragslider({ + range: true, + min: self.isVDMCategory() ? 0 : -120, + max: self.isVDMCategory() ? 100 : -85, + rangeDrag: true, + values: [self.minThreshold, self.maxThreshold], + slide: function (event, ui) { + self.minThreshold = ui.values[0]; + self.maxThreshold = ui.values[1]; + $("#min-range").html(" < " + self.minThreshold + self.thresholdUnit + " <= "); + $("#max-range").html(" < " + self.maxThreshold + self.thresholdUnit + " <= "); + }, + change: function (event, ui) { + self.changed = true; + } + }); + $("#min-range").html(" < " + self.minThreshold + self.thresholdUnit + " <= "); + $("#max-range").html(" < " + self.maxThreshold + self.thresholdUnit + " <= "); } - this.updateShipTrack = function(){ - aisJsonClient.getShipTrack(selectedStartDate.getTime(), selectedEndDate.getTime(), $('#shiptrackingmmsi').val(), function(trackarray){ - shiptrackactive=true; - lineLayer.removeAllFeatures(); - var previousLat = null; - var previousLon = null; - var nextRed = false; - //For each timespan - $.each(trackarray, function(key, value) { - //For each lat-lon inside timespan - $.each(value.positions, function(key1, value1) { - if(previousLat != null){ - if(nextRed == true){ - self.drawLine(previousLat,previousLon,value1.lat,value1.lon, '#CC0000'); - nextRed=false; - }else{ - self.drawLine(previousLat,previousLon,value1.lat,value1.lon, '#006633'); - } - - } - previousLat=value1.lat; - previousLon=value1.lon; - }); - nextRed=true; - - }); - }); + + + this.updateShipTrack = function () { + aisJsonClient.getShipTrack(selectedStartDate.getTime(), selectedEndDate.getTime(), $('#shiptrackingmmsi').val(), function (trackarray) { + shiptrackactive = true; + lineLayer.removeAllFeatures(); + var previousLat = null; + var previousLon = null; + var nextRed = false; + //For each timespan + $.each(trackarray, function (key, value) { + //For each lat-lon inside timespan + $.each(value.positions, function (key1, value1) { + if (previousLat != null) { + if (nextRed == true) { + self.drawLine(previousLat, previousLon, value1.lat, value1.lon, '#CC0000'); + nextRed = false; + } else { + self.drawLine(previousLat, previousLon, value1.lat, value1.lon, '#006633'); + } + + } + previousLat = value1.lat; + previousLon = value1.lon; + }); + nextRed = true; + + }); + }); } - this.drawLine = function(lat1, lon1, lat2, lon2, color){ - var points = new Array( - new OpenLayers.Geometry.Point(lon1, lat1).transform(new OpenLayers.Projection("EPSG:4326"), map.getProjectionObject()), - new OpenLayers.Geometry.Point(lon2, lat2).transform(new OpenLayers.Projection("EPSG:4326"), map.getProjectionObject()) - ); - var line = new OpenLayers.Geometry.LineString(points); - - var style = { - strokeColor: color, + this.drawLine = function (lat1, lon1, lat2, lon2, color) { + var points = new Array( + new OpenLayers.Geometry.Point(lon1, lat1).transform(new OpenLayers.Projection("EPSG:4326"), map.getProjectionObject()), + new OpenLayers.Geometry.Point(lon2, lat2).transform(new OpenLayers.Projection("EPSG:4326"), map.getProjectionObject()) + ); + var line = new OpenLayers.Geometry.LineString(points); + + var style = { + strokeColor: color, // strokeOpacity: 1.0, - strokeWidth: 5 - }; + strokeWidth: 5 + }; - lineFeature = new OpenLayers.Feature.Vector(line, null, style); - lineLayer.addFeatures([lineFeature]); + lineFeature = new OpenLayers.Feature.Vector(line, null, style); + lineLayer.addFeatures([lineFeature]); } - this.updateSlidingWindow = function(){ - aisJsonClient.getStatus(function(data){ - var startDate = new Date(data.firstMessage); - startDate.setMinutes(0); - startDate.setSeconds(0); - startDate.setMilliseconds(0); - var endDate = new Date(data.lastMessage+(1000*60*60)); - endDate.setMinutes(0); - endDate.setSeconds(0); - endDate.setMilliseconds(0); - - self.setupSlidingWindow(startDate, endDate); + this.updateSlidingWindow = function () { + aisJsonClient.getStatus(function (data) { + var startDate = new Date(data.firstMessage); + startDate.setMinutes(0); + startDate.setSeconds(0); + startDate.setMilliseconds(0); + var endDate = new Date(data.lastMessage + (1000 * 60 * 60)); + endDate.setMinutes(0); + endDate.setSeconds(0); + endDate.setMilliseconds(0); + + self.setupSlidingWindow(startDate, endDate); // alert(startDate); // alert(endDate); // alert(data.firstMessage); - }); + }); } - - this.setupSlidingWindow = function(startDate, endDate){ - $('.slidingWindowLabel').hide(); - $( "#globalStarTime" ).html(self.formatDate(startDate)); - $( "#globalEndTime" ).html(self.formatDate(endDate)); - - - //Update export post form - $('#exportStartTime').val(startDate.getTime()); - $('#exportEndTime').val(endDate.getTime()); - - var timeDif = (endDate.getTime()-startDate.getTime())/1000/60/60; - var startTimeLabel = $( "#starTime" ); - var endTimeLabel = $( "#endTime" ); - var intervalLabel = $( "#interval" ); + + this.setupSlidingWindow = function (startDate, endDate) { + $('.slidingWindowLabel').hide(); + $("#globalStarTime").html(self.formatDate(startDate)); + $("#globalEndTime").html(self.formatDate(endDate)); + + + //Update export post form + $('#exportStartTime').val(startDate.getTime()); + $('#exportEndTime').val(endDate.getTime()); + + var timeDif = (endDate.getTime() - startDate.getTime()) / 1000 / 60 / 60; + var startTimeLabel = $("#starTime"); + var endTimeLabel = $("#endTime"); + var intervalLabel = $("#interval"); // var slidingWindowSlider = $( "#slidingWindowOuter" ).width(); - var leftOffset = 37; - var pixelInterval = $( "#slidingWindowOuter" ).width()/timeDif; - $( "#globalStarTime" ).css("left", leftOffset); - $( "#globalEndTime" ).css("left", leftOffset+(pixelInterval*timeDif)); - - - - var updateLabels = function(firstValue, lastValue){ - selectedStartDate = new Date(startDate.getTime()+firstValue*1000*60*60); - selectedEndDate = new Date(startDate.getTime()+lastValue*1000*60*60); - startTimeLabel.html(self.formatDate(selectedStartDate)); - startTimeLabel.css("left", (firstValue*pixelInterval)+leftOffset); - endTimeLabel.html(self.formatDate(selectedEndDate)); - endTimeLabel.css("left", (lastValue*pixelInterval)+leftOffset); - - var intervalSize = lastValue-firstValue; - if(intervalSize > 1){ - intervalLabel.html(intervalSize +" hours"); - }else{ - intervalLabel.html(intervalSize +" hour"); - } - intervalLabel.css("left", ((firstValue+(intervalSize/2))*pixelInterval)-25); - - - } - if (typeof defaultFirstValue === "undefined") { - defaultFirstValue = timeDif-6; - defaultSecondValue = timeDif; - if(timeDif < 6){ defaultFirstValue = 0; } - - }else{ - //console.log(selectedStartDate); - } - updateLabels(defaultFirstValue,defaultSecondValue); - - - $( "#slidingWindow" ).dragslider({ - range: true, - min: 0, - max: timeDif, - rangeDrag: true, - values: [ defaultFirstValue, defaultSecondValue ], - slide: function(event, ui){ - if(ui.values[1] - ui.values[0] < 1){ + var leftOffset = 37; + var pixelInterval = $("#slidingWindowOuter").width() / timeDif; + $("#globalStarTime").css("left", leftOffset); + $("#globalEndTime").css("left", leftOffset + (pixelInterval * timeDif)); + + + var updateLabels = function (firstValue, lastValue) { + selectedStartDate = new Date(startDate.getTime() + firstValue * 1000 * 60 * 60); + selectedEndDate = new Date(startDate.getTime() + lastValue * 1000 * 60 * 60); + startTimeLabel.html(self.formatDate(selectedStartDate)); + startTimeLabel.css("left", (firstValue * pixelInterval) + leftOffset); + endTimeLabel.html(self.formatDate(selectedEndDate)); + endTimeLabel.css("left", (lastValue * pixelInterval) + leftOffset); + + var intervalSize = lastValue - firstValue; + if (intervalSize > 1) { + intervalLabel.html(intervalSize + " hours"); + } else { + intervalLabel.html(intervalSize + " hour"); + } + intervalLabel.css("left", ((firstValue + (intervalSize / 2)) * pixelInterval) - 25); + + + } + if (typeof defaultFirstValue === "undefined") { + defaultFirstValue = timeDif - 6; + defaultSecondValue = timeDif; + if (timeDif < 6) { + defaultFirstValue = 0; + } + + } else { + //console.log(selectedStartDate); + } + updateLabels(defaultFirstValue, defaultSecondValue); + + + $("#slidingWindow").dragslider({ + range: true, + min: 0, + max: timeDif, + rangeDrag: true, + values: [defaultFirstValue, defaultSecondValue], + slide: function (event, ui) { + if (ui.values[1] - ui.values[0] < 1) { // do not allow change return false; } - self.canUpdate = true; - updateLabels(ui.values[ 0 ],ui.values[ 1 ]); - }, - change: function(event, ui){ - - //Update export post form - $('#exportStartTime').val(selectedStartDate.getTime()); - $('#exportEndTime').val(selectedEndDate.getTime()); - - //Update sat bar chart if it is visible - if ($('#barchartpanel').css('display') != 'none') { - self.updateBarChart(); - }else if($('#trackingBarchartWindow').css('display') != 'none'){ - self.updateShipTrackBarChart(); - } - if(shiptrackactive==true){ - self.updateShipTrack(); - } - if(self.canUpdate){ - self.changed = true; - self.canUpdate = false; - } - - } - }); - - showLabels = false; - $('#slidingWindowPanel').mouseover(function() { - if(showLabels==false){ - $('.slidingWindowLabel').fadeIn(100); - } - showLabels=true; - - }); - $('#slidingWindowPanel').mouseout(function() { - showLabels = false; - - //wait a bit and hide if showlabels is still true. - setTimeout(function(){ - if(showLabels==false){ - $('.slidingWindowLabel').fadeOut(100); - } - },1000); - }); - - console.log("sliding window updated"); + self.canUpdate = true; + updateLabels(ui.values[0], ui.values[1]); + }, + change: function (event, ui) { + + //Update export post form + $('#exportStartTime').val(selectedStartDate.getTime()); + $('#exportEndTime').val(selectedEndDate.getTime()); + + //Update sat bar chart if it is visible + if ($('#barchartpanel').css('display') != 'none') { + self.updateBarChart(); + } else if ($('#trackingBarchartWindow').css('display') != 'none') { + self.updateShipTrackBarChart(); + } + if (shiptrackactive == true) { + self.updateShipTrack(); + } + if (self.canUpdate) { + self.changed = true; + self.canUpdate = false; + } + + } + }); + + showLabels = false; + $('#slidingWindowPanel').mouseover(function () { + if (showLabels == false) { + $('.slidingWindowLabel').fadeIn(100); + } + showLabels = true; + + }); + $('#slidingWindowPanel').mouseout(function () { + showLabels = false; + + //wait a bit and hide if showlabels is still true. + setTimeout(function () { + if (showLabels == false) { + $('.slidingWindowLabel').fadeOut(100); + } + }, 1000); + }); + + console.log("sliding window updated"); } - this.formatDate = function(date){ - return date.toISOString(); + this.formatDate = function (date) { + return date.toISOString(); } - + /** * Adds all the layers that will contain graphic. */ - this.addLayers = function(){ - - // Get renderer - var renderer = OpenLayers.Util.getParameters(window.location.href).renderer; - renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers; - // renderer = ["Canvas", "SVG", "VML"]; - - // Add OpenStreetMap Layer - var osm = new OpenLayers.Layer.OSM( - "OSM", - "http://a.tile.openstreetmap.org/${z}/${x}/${y}.png", - { - 'layers':'basic', - 'isBaseLayer': true - } - ); - map.addLayer(osm); - - //polygon layer - polygonLayer = new OpenLayers.Layer.Vector("Polygon Layer",{ - styleMap: new OpenLayers.StyleMap({ - "default": new OpenLayers.Style({ - strokeColor: "black", - strokeOpacity: .7, - strokeWidth: 1, - fillColor: "${fillcolor}", - fillOpacity: .6, - cursor: "pointer" - }), - "select": new OpenLayers.Style({ - strokeColor: "blue", - strokeOpacity: .7, - strokeWidth: 4, - }) - }) - }); + this.addLayers = function () { + + // Get renderer + var renderer = OpenLayers.Util.getParameters(window.location.href).renderer; + renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers; + // renderer = ["Canvas", "SVG", "VML"]; + + // Add OpenStreetMap Layer + var osm = new OpenLayers.Layer.OSM( + "OSM", + "http://a.tile.openstreetmap.org/${z}/${x}/${y}.png", + { + 'layers': 'basic', + 'isBaseLayer': true + } + ); + map.addLayer(osm); + + //polygon layer + polygonLayer = new OpenLayers.Layer.Vector("Polygon Layer", { + styleMap: new OpenLayers.StyleMap({ + "default": new OpenLayers.Style({ + strokeColor: "black", + strokeOpacity: .7, + strokeWidth: 1, + fillColor: "${fillcolor}", + fillOpacity: .6, + cursor: "pointer" + }), + "select": new OpenLayers.Style({ + strokeColor: "blue", + strokeOpacity: .7, + strokeWidth: 4, + }) + }) + }); // polygonLayer.styleMap.styles['default'].defaultStyle={fillColor: 'blue'} - map.addLayers([polygonLayer]); - - //source layer - sourceLayer = new OpenLayers.Layer.Vector("Sources", { - styleMap: new OpenLayers.StyleMap({ - "default": new OpenLayers.Style({ - strokeColor: "black", - strokeOpacity: .7, - strokeWidth: 1, - fillColor: "${fillcolor}", - fillOpacity: .6, - cursor: "pointer" - }), - "select": new OpenLayers.Style({ - strokeColor: "blue", - strokeOpacity: .7, - strokeWidth: 4, - }) - }) - }); - map.addLayer(sourceLayer); - - //select control for source layer - selectControl = new OpenLayers.Control.SelectFeature([sourceLayer, polygonLayer], - { - clickout: true, - hover: false, - highlightOnly: false, - eventListeners: { - featurehighlighted: self.onFeatureSelect, - featureunhighlighted: self.onFeatureUnselect - } - - } - ); - selectControl.handlers.feature.stopDown = false; - map.addControl(selectControl); - selectControl.activate(); - - boxLayer = new OpenLayers.Layer.Vector("Box layer"); - map.addLayers([boxLayer]); + map.addLayers([polygonLayer]); + + //source layer + sourceLayer = new OpenLayers.Layer.Vector("Sources", { + styleMap: new OpenLayers.StyleMap({ + "default": new OpenLayers.Style({ + strokeColor: "black", + strokeOpacity: .7, + strokeWidth: 1, + fillColor: "${fillcolor}", + fillOpacity: .6, + cursor: "pointer" + }), + "select": new OpenLayers.Style({ + strokeColor: "blue", + strokeOpacity: .7, + strokeWidth: 4, + }) + }) + }); + map.addLayer(sourceLayer); + + //select control for source layer + selectControl = new OpenLayers.Control.SelectFeature([sourceLayer, polygonLayer], + { + clickout: true, + hover: false, + highlightOnly: false, + eventListeners: { + featurehighlighted: self.onFeatureSelect, + featureunhighlighted: self.onFeatureUnselect + } + + } + ); + selectControl.handlers.feature.stopDown = false; + map.addControl(selectControl); + selectControl.activate(); + + boxLayer = new OpenLayers.Layer.Vector("Box layer"); + map.addLayers([boxLayer]); // var currentBox = null; - var draw = new OpenLayers.Control.DrawFeature( - boxLayer, - OpenLayers.Handler.RegularPolygon, - { - featureAdded : function(feature) { - if(boxLayer.features.length > 1){ - boxLayer.removeFeatures([boxLayer.features[0]]); + var draw = new OpenLayers.Control.DrawFeature( + boxLayer, + OpenLayers.Handler.RegularPolygon, + { + featureAdded: function (feature) { + if (boxLayer.features.length > 1) { + boxLayer.removeFeatures([boxLayer.features[0]]); //// console.log("remove"); - } + } // currentBox = feature; - var g=boxLayer.features[0].clone().geometry; //get geometry of a featyre in your vector layer - var vertices = g.getVertices(); - - topleftPoint = vertices[1].transform( map.getProjectionObject(), - new OpenLayers.Projection("EPSG:4326")); - bottomRightPoint = vertices[3].transform( map.getProjectionObject(), - new OpenLayers.Projection("EPSG:4326")); - - self.updateBarChart(); - $('#barchartpanel').fadeIn(100); + var g = boxLayer.features[0].clone().geometry; //get geometry of a featyre in your vector layer + var vertices = g.getVertices(); + + topleftPoint = vertices[1].transform(map.getProjectionObject(), + new OpenLayers.Projection("EPSG:4326")); + bottomRightPoint = vertices[3].transform(map.getProjectionObject(), + new OpenLayers.Projection("EPSG:4326")); + + self.updateBarChart(); + $('#barchartpanel').fadeIn(100); // console.log("A point has been added"); - }, - handlerOptions: { - sides: 4, - irregular: true - } - },function(){alert('added')} - ); - map.addControl(draw); - - var isDown = false; - var box; - $(document).keydown(function (e) { - if(e.which == 17){ - if(isDown != true){ - isDown = true; - draw.activate(); + }, + handlerOptions: { + sides: 4, + irregular: true + } + }, function () { + alert('added') + } + ); + map.addControl(draw); + + var isDown = false; + var box; + $(document).keydown(function (e) { + if (e.which == 17) { + if (isDown != true) { + isDown = true; + draw.activate(); // $('.expandable').fadeOut(); - } - isDown = true; - - } - }); - $(document).keyup(function (e) { - if(e.which == 17){ - isDown = false; - draw.deactivate(); + } + isDown = true; + + } + }); + $(document).keyup(function (e) { + if (e.which == 17) { + isDown = false; + draw.deactivate(); // $('.expandable').fadeIn(); - } - }); - - lineLayer = new OpenLayers.Layer.Vector("Line Layer"); - map.addLayers([lineLayer]); + } + }); + + lineLayer = new OpenLayers.Layer.Vector("Line Layer"); + map.addLayers([lineLayer]); } - this.updateBarChart = function(){ - $('#trackingBarchartWindow').hide(); - $('#barchart').attr('src', 'rest/satExportPNG?random='+Math.random()+'&startTime='+selectedStartDate.getTime()+'&endTime='+selectedEndDate.getTime()+'&lat1='+topleftPoint.y+'&lon1='+topleftPoint.x+'&lat2='+bottomRightPoint.y+'&lon2='+bottomRightPoint.x+'&satChartMethod='+satChartMethod); + this.updateBarChart = function () { + $('#trackingBarchartWindow').hide(); + $('#barchart').attr('src', 'rest/satExportPNG?random=' + Math.random() + '&startTime=' + selectedStartDate.getTime() + '&endTime=' + selectedEndDate.getTime() + '&lat1=' + topleftPoint.y + '&lon1=' + topleftPoint.x + '&lat2=' + bottomRightPoint.y + '&lon2=' + bottomRightPoint.x + '&satChartMethod=' + satChartMethod); } - this.updateShipTrackBarChart = function(){ - $('#barchartpanel').hide(); - $('#trackingBarchartImg').attr('src', 'rest/shipTrackExportPNG?random='+Math.random()+'&startTime='+selectedStartDate.getTime()+'&endTime='+selectedEndDate.getTime()+'&shipmmsi='+$('#shiptrackingmmsi').val()); + this.updateShipTrackBarChart = function () { + $('#barchartpanel').hide(); + $('#trackingBarchartImg').attr('src', 'rest/shipTrackExportPNG?random=' + Math.random() + '&startTime=' + selectedStartDate.getTime() + '&endTime=' + selectedEndDate.getTime() + '&shipmmsi=' + $('#shiptrackingmmsi').val()); } - this.refreshSourceList = function(){ - var sourceContainer = $("#sourcesPanel > .panelContainer"); - sourceContainer.html(""); - var sourceshtml = ""; - $.each(self.sources, function(key, source) { - var checked = ""; - if(source.enabled){ - checked = 'checked="checked"'; - } - sourceshtml += '
'+source.name+'
'+source.type+'
'; - }); - sourceContainer.html(sourceshtml); - self.drawSources(); + this.refreshSourceList = function () { + var sourceContainer = $("#sourcesPanel > .panelContainer"); + sourceContainer.html(""); + var sourceshtml = ""; + $.each(self.sources, function (key, source) { + var checked = ""; + if (source.enabled) { + checked = 'checked="checked"'; + } + sourceshtml += '
' + source.name + '
' + source.type + '
'; + }); + sourceContainer.html(sourceshtml); + self.drawSources(); } - - this.refreshSourceDetails = function(){ - - if(self.selectedSource == null){ - return; - } - var checked=""; - if(self.selectedSource.enabled){ - checked='checked="checked"'; + + this.refreshSourceDetails = function () { + + if (self.selectedSource == null) { + return; + } + var checked = ""; + if (self.selectedSource.enabled) { + checked = 'checked="checked"'; } - $("#featureDetailsPanel > .panelContainer").html('
Id
'+ - '
'+feature.mmsi+'
'+ - '
Name
'+ - '
'+feature.name+'
'+ - '
Type
'+ - '
'+feature.type+'
'+ - '
Lat
'+ - '
'+feature.lat.toFixed(4)+'
'+ - '
Lon
'+ - '
'+feature.lon.toFixed(4)+'
'+ - '
Enabled
'+ - '
'); + $("#featureDetailsPanel > .panelContainer").html('
Id
' + + '
' + feature.mmsi + '
' + + '
Name
' + + '
' + feature.name + '
' + + '
Type
' + + '
' + feature.type + '
' + + '
Lat
' + + '
' + feature.lat.toFixed(4) + '
' + + '
Lon
' + + '
' + feature.lon.toFixed(4) + '
' + + '
Enabled
' + + '
'); } - - this.drawSources = function(){ - sourceLayer.removeAllFeatures(); - var drawTheSource = this.drawSource - $.each(this.sources, function(key, val) { - drawTheSource(val); - }); + + this.drawSources = function () { + sourceLayer.removeAllFeatures(); + var drawTheSource = this.drawSource + $.each(this.sources, function (key, val) { + drawTheSource(val); + }); } - - this.drawSource = function(val){ - var image; - if(val.enabled){ - image = 'img/marker.png'; - }else{ - image = 'img/marker2.png' - } - - var feature = new OpenLayers.Feature.Vector( - new OpenLayers.Geometry.Point( val.lon , val.lat ).transform( - new OpenLayers.Projection("EPSG:4326"), // transform from WGS 1984 - map.getProjectionObject() // to Spherical Mercator Projection - ), - {hmm:'100'}); + + this.drawSource = function (val) { + var image; + if (val.enabled) { + image = 'img/marker.png'; + } else { + image = 'img/marker2.png' + } + + var feature = new OpenLayers.Feature.Vector( + new OpenLayers.Geometry.Point(val.lon, val.lat).transform( + new OpenLayers.Projection("EPSG:4326"), // transform from WGS 1984 + map.getProjectionObject() // to Spherical Mercator Projection + ), + {hmm: '100'}); // {externalGraphic: image, graphicHeight: 21, graphicWidth: 16, cursor: "crosshair", fillColor: "#ffcc66", pointRadius: "10"}); - feature.mmsi = val.mmsi; - feature.name = val.name; - feature.type = val.type; - feature.lat = val.lat; - feature.lon = val.lon; - feature.source = val; - val.feature=feature; - - if(val.enabled){ - feature.style = { - pointRadius: "10", // sized according to type attribute - fillColor: "#980000 ", - strokeColor: "black", - strokeWidth: 2, - graphicZIndex: 1, - cursor: "pointer" - } - }else{ - feature.style = { - pointRadius: "10", // sized according to type attribute - fillColor: "white", - strokeColor: "black", - strokeWidth: 2, - graphicZIndex: 1, - cursor: "pointer" - } - } - if(self.selectedSource == val){ - feature.style["strokeColor"] = "blue"; - }else{ - feature.style["strokeColor"] = "black"; - } - - sourceLayer.addFeatures(feature); + feature.mmsi = val.mmsi; + feature.name = val.name; + feature.type = val.type; + feature.lat = val.lat; + feature.lon = val.lon; + feature.source = val; + val.feature = feature; + + if (val.enabled) { + feature.style = { + pointRadius: "10", // sized according to type attribute + fillColor: "#980000 ", + strokeColor: "black", + strokeWidth: 2, + graphicZIndex: 1, + cursor: "pointer" + } + } else { + feature.style = { + pointRadius: "10", // sized according to type attribute + fillColor: "white", + strokeColor: "black", + strokeWidth: 2, + graphicZIndex: 1, + cursor: "pointer" + } + } + if (self.selectedSource == val) { + feature.style["strokeColor"] = "blue"; + } else { + feature.style["strokeColor"] = "black"; + } + + sourceLayer.addFeatures(feature); } - + // this.loadSatStats = function(topleft, bottomright){ // //// var array = [{"fromTime":1374220785115,"toTime":1374220774501,"spanLength":1,"timeSinceLastSpan":0,"accumulatedTime":0,"signals":3,"distinctShips":1},{"fromTime":1374226711355,"toTime":1374227266676,"spanLength":9,"timeSinceLastSpan":98,"accumulatedTime":108,"signals":163,"distinctShips":7},{"fromTime":1374228081000,"toTime":1374228352000,"spanLength":4,"timeSinceLastSpan":13,"accumulatedTime":126,"signals":38,"distinctShips":6},{"fromTime":1374232884293,"toTime":1374233250297,"spanLength":6,"timeSinceLastSpan":75,"accumulatedTime":207,"signals":71,"distinctShips":6},{"fromTime":1374233909000,"toTime":1374234299000,"spanLength":6,"timeSinceLastSpan":10,"accumulatedTime":225,"signals":33,"distinctShips":7},{"fromTime":1374238552052,"toTime":1374239226409,"spanLength":11,"timeSinceLastSpan":70,"accumulatedTime":307,"signals":46,"distinctShips":6},{"fromTime":1374239872000,"toTime":1374239943000,"spanLength":1,"timeSinceLastSpan":10,"accumulatedTime":319,"signals":8,"distinctShips":5},{"fromTime":1374243070626,"toTime":1374243403490,"spanLength":5,"timeSinceLastSpan":52,"accumulatedTime":377,"signals":42,"distinctShips":6},{"fromTime":1374244812816,"toTime":1374245655000,"spanLength":14,"timeSinceLastSpan":23,"accumulatedTime":414,"signals":105,"distinctShips":6},{"fromTime":1374248972125,"toTime":1374249122001,"spanLength":2,"timeSinceLastSpan":55,"accumulatedTime":472,"signals":58,"distinctShips":7},{"fromTime":1374250432644,"toTime":1374251214883,"spanLength":13,"timeSinceLastSpan":21,"accumulatedTime":507,"signals":120,"distinctShips":7},{"fromTime":1374251846000,"toTime":1374251977000,"spanLength":2,"timeSinceLastSpan":10,"accumulatedTime":520,"signals":5,"distinctShips":4},{"fromTime":1374254798441,"toTime":1374255211329,"spanLength":6,"timeSinceLastSpan":47,"accumulatedTime":573,"signals":33,"distinctShips":6},{"fromTime":1374257362000,"toTime":1374257820000,"spanLength":7,"timeSinceLastSpan":35,"accumulatedTime":617,"signals":16,"distinctShips":7},{"fromTime":1374261058127,"toTime":1374261077807,"spanLength":1,"timeSinceLastSpan":53,"accumulatedTime":671,"signals":2,"distinctShips":1},{"fromTime":1374263221000,"toTime":1374263407000,"spanLength":3,"timeSinceLastSpan":35,"accumulatedTime":710,"signals":16,"distinctShips":5},{"fromTime":1374269013000,"toTime":1374269185000,"spanLength":2,"timeSinceLastSpan":93,"accumulatedTime":806,"signals":6,"distinctShips":2},{"fromTime":1374274821000,"toTime":1374274933000,"spanLength":1,"timeSinceLastSpan":93,"accumulatedTime":902,"signals":4,"distinctShips":4},{"fromTime":1374278293629,"toTime":1374278393470,"spanLength":1,"timeSinceLastSpan":56,"accumulatedTime":960,"signals":4,"distinctShips":3},{"fromTime":1374220785115,"toTime":1374220774501,"spanLength":1,"timeSinceLastSpan":33,"accumulatedTime":0,"signals":3,"distinctShips":1},{"fromTime":1374226711355,"toTime":1374227266676,"spanLength":9,"timeSinceLastSpan":98,"accumulatedTime":108,"signals":163,"distinctShips":7},{"fromTime":1374228081000,"toTime":1374228352000,"spanLength":4,"timeSinceLastSpan":13,"accumulatedTime":126,"signals":38,"distinctShips":6},{"fromTime":1374232884293,"toTime":1374233250297,"spanLength":6,"timeSinceLastSpan":75,"accumulatedTime":207,"signals":71,"distinctShips":6},{"fromTime":1374233909000,"toTime":1374234299000,"spanLength":6,"timeSinceLastSpan":10,"accumulatedTime":225,"signals":33,"distinctShips":7},{"fromTime":1374238552052,"toTime":1374239226409,"spanLength":11,"timeSinceLastSpan":70,"accumulatedTime":307,"signals":46,"distinctShips":6},{"fromTime":1374239872000,"toTime":1374239943000,"spanLength":1,"timeSinceLastSpan":10,"accumulatedTime":319,"signals":8,"distinctShips":5},{"fromTime":1374243070626,"toTime":1374243403490,"spanLength":5,"timeSinceLastSpan":52,"accumulatedTime":377,"signals":42,"distinctShips":6},{"fromTime":1374244812816,"toTime":1374245655000,"spanLength":14,"timeSinceLastSpan":23,"accumulatedTime":414,"signals":105,"distinctShips":6},{"fromTime":1374248972125,"toTime":1374249122001,"spanLength":2,"timeSinceLastSpan":55,"accumulatedTime":472,"signals":58,"distinctShips":7},{"fromTime":1374250432644,"toTime":1374251214883,"spanLength":13,"timeSinceLastSpan":21,"accumulatedTime":507,"signals":120,"distinctShips":7},{"fromTime":1374251846000,"toTime":1374251977000,"spanLength":2,"timeSinceLastSpan":10,"accumulatedTime":520,"signals":5,"distinctShips":4},{"fromTime":1374254798441,"toTime":1374255211329,"spanLength":6,"timeSinceLastSpan":47,"accumulatedTime":573,"signals":33,"distinctShips":6},{"fromTime":1374257362000,"toTime":1374257820000,"spanLength":7,"timeSinceLastSpan":35,"accumulatedTime":617,"signals":16,"distinctShips":7},{"fromTime":1374261058127,"toTime":1374261077807,"spanLength":1,"timeSinceLastSpan":53,"accumulatedTime":671,"signals":2,"distinctShips":1},{"fromTime":1374263221000,"toTime":1374263407000,"spanLength":3,"timeSinceLastSpan":35,"accumulatedTime":710,"signals":16,"distinctShips":5},{"fromTime":1374269013000,"toTime":1374269185000,"spanLength":2,"timeSinceLastSpan":93,"accumulatedTime":806,"signals":6,"distinctShips":2},{"fromTime":1374274821000,"toTime":1374274933000,"spanLength":1,"timeSinceLastSpan":93,"accumulatedTime":902,"signals":4,"distinctShips":4},{"fromTime":1374278293629,"toTime":1374379393470,"spanLength":1,"timeSinceLastSpan":56,"accumulatedTime":960,"signals":4,"distinctShips":3}]; @@ -717,179 +755,222 @@ function CoverageUI () { //// alert(topleft); // } - this.drawCoverage = function(){ - - //get the multiplication factor for corresponding zoom level - var multifactor = self.getMultiplicationFactor(); + this.drawCoverage = function () { + + //get the multiplication factor for corresponding zoom level + var multifactor = self.getMultiplicationFactor(); // multifactor = 5; - $('#multiplicationFactor').html(multifactor); - - // activate loading panel - $("#loadingPanel").css('visibility', 'visible'); - - // get lat lon points for each screen corner - var topleftpixel = new OpenLayers.Pixel(0, 0); - var bottomrightpixel = new OpenLayers.Pixel($("#map").width(), $("#map").height()); - var topleftlonlat = map.getLonLatFromPixel(topleftpixel).transform( - map.getProjectionObject(), // from Spherical Mercator Projection - new OpenLayers.Projection("EPSG:4326") // to WGS 1984 - ); - var bottomrightlonlat = map.getLonLatFromPixel(bottomrightpixel).transform( - map.getProjectionObject(), // from Spherical Mercator Projection - new OpenLayers.Projection("EPSG:4326") // to WGS 1984 - ); - var screenarea = topleftlonlat.lat.toFixed(4)+","+topleftlonlat.lon.toFixed(4)+","+bottomrightlonlat.lat.toFixed(4)+","+bottomrightlonlat.lon.toFixed(4); - - //convert enabled sources to string to be sent - var dataToBeSent = self.enabledSourcesToString(); - - - - //use json client to fetch data from web service - aisJsonClient.getCoverage(dataToBeSent, screenarea, multifactor,selectedStartDate.getTime(), selectedEndDate.getTime(), function(data){ - - //remove existing cells - polygonLayer.removeAllFeatures(); - - $('#latSize').html(data.latSize.toFixed(4) + " degrees"); - $('#lonSize').html(data.lonSize.toFixed(4) + " degrees"); - - var minExpectedMessages = - $.each(data.cells, function(key, val) { - var points = [ - new OpenLayers.Geometry.Point(val.lon, val.lat), - new OpenLayers.Geometry.Point(val.lon, val.lat+data.latSize), - new OpenLayers.Geometry.Point(val.lon+data.lonSize, val.lat+data.latSize), - new OpenLayers.Geometry.Point(val.lon+data.lonSize, val.lat) - ]; - var expectedMessages = (val.nrOfMisMes+val.nrOfRecMes); - var coverageValue = val.nrOfRecMes/expectedMessages; - var color; - if(expectedMessages >= self.minExpectedMessages){ - if(coverageValue >= self.maxThreshold/100){ - color ='green'; - }else if(coverageValue >= self.minThreshold/100){ - color ='yellow'; - }else{ - color ='red'; - } - - self.drawPolygon({ - lat: val.lat, - lon: val.lon, - points: points, - fillcolor: color, - totalMessages: expectedMessages, - receivedMessages: val.nrOfRecMes - }); - } - }); + $('#multiplicationFactor').html(multifactor); + + // activate loading panel + $("#loadingPanel").css('visibility', 'visible'); + + // get lat lon points for each screen corner + var topleftpixel = new OpenLayers.Pixel(0, 0); + var bottomrightpixel = new OpenLayers.Pixel($("#map").width(), $("#map").height()); + var topleftlonlat = map.getLonLatFromPixel(topleftpixel).transform( + map.getProjectionObject(), // from Spherical Mercator Projection + new OpenLayers.Projection("EPSG:4326") // to WGS 1984 + ); + var bottomrightlonlat = map.getLonLatFromPixel(bottomrightpixel).transform( + map.getProjectionObject(), // from Spherical Mercator Projection + new OpenLayers.Projection("EPSG:4326") // to WGS 1984 + ); + var screenarea = topleftlonlat.lat.toFixed(4) + "," + topleftlonlat.lon.toFixed(4) + "," + bottomrightlonlat.lat.toFixed(4) + "," + bottomrightlonlat.lon.toFixed(4); + + //convert enabled sources to string to be sent + var dataToBeSent = self.enabledSourcesToString(); + + + //use json client to fetch data from web service + aisJsonClient.getCoverage(dataToBeSent, screenarea, multifactor, selectedStartDate.getTime(), selectedEndDate.getTime(), function (data) { + + //remove existing cells + polygonLayer.removeAllFeatures(); + + $('#latSize').html(data.latSize.toFixed(4) + " degrees"); + $('#lonSize').html(data.lonSize.toFixed(4) + " degrees"); + + + var minExpectedMessages = + $.each(data.cells, function (key, val) { + var points = [ + new OpenLayers.Geometry.Point(val.lon, val.lat), + new OpenLayers.Geometry.Point(val.lon, val.lat + data.latSize), + new OpenLayers.Geometry.Point(val.lon + data.lonSize, val.lat + data.latSize), + new OpenLayers.Geometry.Point(val.lon + data.lonSize, val.lat) + ]; + var totalMessages = 0; + var coverageValue = 0; + + if (self.isVDMCategory()) { + totalMessages = (val.nrOfMisMes + val.nrOfRecMes); + coverageValue = val.nrOfRecMes / totalMessages; + } else { + totalMessages = val.numberOfVsiMessages; + coverageValue = val.averageSignalStrength; + } + + computeThresholdValue = function (threshold) { + if (self.isVDMCategory()) { + return threshold / 100; + } else { + return threshold; + } + }; + + var color; + if (totalMessages >= self.minExpectedMessages) { + if (coverageValue >= computeThresholdValue(self.maxThreshold)) { + color = 'green'; + } else if (coverageValue >= computeThresholdValue(self.minThreshold)) { + color = 'yellow'; + } else { + color = 'red'; + } + + self.drawPolygon({ + lat: val.lat, + lon: val.lon, + points: points, + fillcolor: color, + totalMessages: totalMessages, + receivedMessages: val.nrOfRecMes, + averageSignalStrength: val.averageSignalStrength + }); + } + }); + // self.loading = false; - $("#loadingPanel").css('visibility', 'hidden'); - }); + $("#loadingPanel").css('visibility', 'hidden'); + }); } - - this.drawPolygon = function(options){ - var site_points = []; - for (i in options.points) { - options.points[i].transform( - new OpenLayers.Projection("EPSG:4326"), // transform from WGS 1984 - map.getProjectionObject() // to Spherical Mercator Projection - ); - site_points.push(options.points[i]); - } - var linear_ring = new OpenLayers.Geometry.LinearRing(site_points); - polygonFeature = new OpenLayers.Feature.Vector( - new OpenLayers.Geometry.Polygon([linear_ring]),null); - polygonFeature.attributes.fillcolor = options.fillcolor; - polygonFeature.type = "cell"; - polygonFeature.lon = options.lon; - polygonFeature.lat = options.lat; - polygonFeature.totalMessages = options.totalMessages; - polygonFeature.receivedMessages = options.receivedMessages; - polygonLayer.addFeatures([polygonFeature]); - + + this.drawPolygon = function (options) { + var site_points = []; + for (i in options.points) { + options.points[i].transform( + new OpenLayers.Projection("EPSG:4326"), // transform from WGS 1984 + map.getProjectionObject() // to Spherical Mercator Projection + ); + site_points.push(options.points[i]); + } + var linear_ring = new OpenLayers.Geometry.LinearRing(site_points); + polygonFeature = new OpenLayers.Feature.Vector( + new OpenLayers.Geometry.Polygon([linear_ring]), null); + polygonFeature.attributes.fillcolor = options.fillcolor; + polygonFeature.type = "cell"; + polygonFeature.lon = options.lon; + polygonFeature.lat = options.lat; + polygonFeature.totalMessages = options.totalMessages; + polygonFeature.receivedMessages = options.receivedMessages; + polygonFeature.averageSignalStrength = options.averageSignalStrength; + polygonLayer.addFeatures([polygonFeature]); + } - - this.enabledSourcesToString = function(){ - var string=""; - $.each(this.sources, function(key, source) { - if(source.enabled){ - string += source.mmsi+","; - } - }); - return string; + + this.enabledSourcesToString = function () { + var string = ""; + $.each(this.sources, function (key, source) { + if (source.enabled) { + string += source.mmsi + ","; + } + }); + return string; } - - this.getMultiplicationFactor = function(){ - var zLevel = map.getZoom(); - var multifactor; - if(zLevel > 10){ - multifactor = 1; - }else if(zLevel == 10){ - multifactor = 1; - }else if(zLevel == 9){ - multifactor = 2; - }else if(zLevel == 8){ - multifactor = 3; - }else if(zLevel == 7){ - multifactor = 4; - }else if(zLevel == 6){ - multifactor = 8; - }else if(zLevel == 5){ - multifactor = 20; - }else if(zLevel == 4){ - multifactor = 40; - }else if(zLevel == 3){ - multifactor = 60; - }else if(zLevel == 2){ - multifactor = 60; - }else if(zLevel == 1){ - multifactor = 80; + + this.getMultiplicationFactor = function () { + var zLevel = map.getZoom(); + var multifactor; + if (zLevel > 10) { + multifactor = 1; + } else if (zLevel == 10) { + multifactor = 1; + } else if (zLevel == 9) { + multifactor = 2; + } else if (zLevel == 8) { + multifactor = 3; + } else if (zLevel == 7) { + multifactor = 4; + } else if (zLevel == 6) { + multifactor = 8; + } else if (zLevel == 5) { + multifactor = 20; + } else if (zLevel == 4) { + multifactor = 40; + } else if (zLevel == 3) { + multifactor = 60; + } else if (zLevel == 2) { + multifactor = 60; + } else if (zLevel == 1) { + multifactor = 80; } // alert(multifactor) - return multifactor; + return multifactor; } - - this.onFeatureSelect = function(evt) { - + + this.onFeatureSelect = function (evt) { + feature = evt.feature; - + //determine if feature is a cell or a source - if(feature.type == "cell"){ - - //remove potential source - self.selectedSource = null; - self.drawSources(); - - //Setting up cell details panel - $("#featureDetailsPanel > .panelHeader").html("Cell Details"); - $("#featureDetailsPanel > .panelContainer").html('
Source
'+ - '
'+feature.mmsi+'
'+ - '
Cell Latitude
'+ - '
'+feature.lat.toFixed(4)+'
'+ - '
Cell Longitude
'+ - '
'+feature.lon.toFixed(4)+'
'+ - '
Received Messages
'+ - '
'+feature.receivedMessages+'
'+ - '
Expected Messages
'+ - '
'+feature.totalMessages+'
'+ - '
Coverage
'+ - '
'+((feature.receivedMessages/feature.totalMessages)*100).toFixed(2)+' %
'); - $("#featureDetailsPanel").slideDown(); - - - }else{ - self.selectedSource = self.sources[feature.mmsi]; + if (feature.type == "cell") { + + //remove potential source + self.selectedSource = null; + self.drawSources(); + + //Setting up cell details panel + $("#featureDetailsPanel > .panelHeader").html("Cell Details"); + $("#featureDetailsPanel > .panelContainer").html(self.drawCellDetails(feature)); + $("#featureDetailsPanel").slideDown(); + + + } else { + self.selectedSource = self.sources[feature.mmsi]; self.refreshSourceDetails(); $("#featureDetailsPanel").slideDown(); $("#featureDetailsPanel > .panelHeader").html("Source Details"); self.drawSources(); } - + } - - this.onFeatureUnselect = function(evt) { + + this.drawCellDetails = function (feature) { + var cellDetail = + '
Source
' + + '
' + feature.mmsi + '
' + + '
Cell Latitude
' + + '
' + feature.lat.toFixed(4) + '
' + + '
Cell Longitude
' + + '
' + feature.lon.toFixed(4) + '
'; + + if (self.isVDMCategory()) { + cellDetail = cellDetail.concat( + '
Received Messages
' + + '
' + feature.receivedMessages + '
' + + '
Expected Messages
' + + '
' + feature.totalMessages + '
' + + '
Coverage
' + + '
' + ((feature.receivedMessages / feature.totalMessages) * 100).toFixed(2) + ' ' + self.thresholdUnit + '
'); + } else { + cellDetail = cellDetail.concat( + '
Number of Messages
' + + '
' + feature.totalMessages + '
' + + '
Average Signal Strength
' + + '
' + feature.averageSignalStrength + ' ' + self.thresholdUnit + '
'); + } + + return cellDetail; + } + + this.cleanupCellDetails = function () { + $("#featureDetailsPanel > .panelHeader").html("Cell Details"); + $("#featureDetailsPanel > .panelContainer").html(""); + $("#featureDetailsPanel").slideUp(); + } + + this.onFeatureUnselect = function (evt) { // feature = evt.feature; // @@ -902,7 +983,7 @@ function CoverageUI () { //// $("#featureDetailsPanel").slideUp(); // self.drawSources(); //// } - + } - -} \ No newline at end of file + +}