/*******************************************************************************
* Copyright 2013-2016 alladin-IT GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package at.alladin.rmbt.statisticServer;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.restlet.data.Form;
import org.restlet.data.Status;
import org.restlet.resource.Get;
import at.alladin.rmbt.shared.Classification;
import at.alladin.rmbt.shared.model.SpeedItems;
import at.alladin.rmbt.shared.model.SpeedItems.SpeedItem;
import com.google.gson.Gson;
public class OpenTestResource extends ServerResource
{
//maximum of rows sent in one single request
public final int MAXROWS = 400;
//all fields that should be displayed in a detailed request
private final String[] openDataFieldsFull = {
"open_uuid", //csv 1
"open_test_uuid", //csv 2
"time", //csv 3
"cat_technology", //csv 4
"network_type", //csv 5
"lat", //csv 6
"long", //csv 7
"loc_src", //csv 8
"loc_accuracy",
"public_ip_as_name",
"zip_code", //csv 9
"gkz",
"community",
"district",
"province",
"cov800cat",
"download_kbit", //csv 10
"upload_kbit", //csv 11
"wifi_link_speed",
"ping_ms", //csv 12
"signal_strength", //csv 13
"lte_rsrp", //csv 29
"lte_rsrq",
"server_name", //csv 14
"implausible", //csv 28
"pinned",
"test_duration", //csv 15
"num_threads_requested",
"num_threads", //csv 16
"num_threads_ul",
"platform", //csv 17
"model", //csv 18
"model_native",
"product",
"client_version", //csv 19
"network_mcc_mnc", //csv 20
"network_country",
"roaming_type",
"network_name", //csv 21
"sim_mcc_mnc", //csv 22
"sim_country",
"provider_name",
"connection", //csv 23
"asn", //csv 24
"ip_anonym", //csv 25
"ndt_download_kbit", //csv 26
"ndt_upload_kbit", //csv 27
"country_geoip",
"country_location",
"country_asn",
"bytes_download",
"bytes_upload",
"test_if_bytes_download",
"test_if_bytes_upload",
"testdl_if_bytes_download",
"testdl_if_bytes_upload",
"testul_if_bytes_download",
"testul_if_bytes_upload",
"duration_download_ms",
"duration_upload_ms",
"time_dl_ms",
"time_ul_ms"
};
//all fields that are numbers (and are formatted as numbers in json)
private final HashSet<String> openDataNumberFields = new HashSet<>(Arrays.asList(new String[]{"time", "lat", "long", "loc_accuracy", "zip_code","gkz","download_kbit",
"upload_kbit","ping_ms","signal_strength","lte_rsrp","lte_rsrq","test_duration","num_threads","ndt_download_kbit","ndt_upload_kbit","asn",
"bytes_download","bytes_upload","test_if_bytes_download","test_if_bytes_upload","testdl_if_bytes_download",
"testdl_if_bytes_upload","testul_if_bytes_download","testul_if_bytes_upload","duration_download_ms","duration_upload_ms","time_dl_ms",
"time_ul_ms", "roaming_type", "num_threads_ul", "num_threads_requested"
}));
//all fields that are boolean
private final HashSet<String> openDataBooleanFields = new HashSet<>(Arrays.asList(new String[] {"implausible", "pinned"}));
@Get("json")
public String request(final String entity)
{
int verboseLevel = 0;
addAllowOrigin();
//routing should be in a way in which open_test_uuid is always set
String openUUID = getRequest().getAttributes().get("open_test_uuid").toString();
final Form getParameters = getRequest().getResourceRef().getQueryAsForm();
//allow sender + verbose
for (String name : getParameters.getNames()) {
if (name.equals("verbose")) {
try {
verboseLevel = Integer.parseInt(getParameters.getFirstValue("verbose"));
}
catch (NumberFormatException ex) {
Logger.getGlobal().info("invalid non-numberic verbosity level");
}
}
else if (name.equals("sender") || name.equals("?sender")) { //allow for ?sender; used by some users due to error in old documentation
//ignore for now
final String sender_id;
if (name.equals("sender"))
sender_id = "sender " + getParameters.getFirstValue("sender");
else
sender_id = "sender " + getParameters.getFirstValue("?sender");
// System.out.println(sender_id);
//the logging block would require write access to the database
/*
final String sql = "UPDATE json_sender SET count = count + 1 WHERE sender_id = ?";
PreparedStatement ps = null;
try
{
ps = conn.prepareStatement(sql);
ps.setString(1, sender_id);
ps.executeUpdate();
} catch (final SQLException e)
{
Logger.getLogger(OpenTestResource.class.getName()).log(Level.SEVERE, null, e);
}
*/
}
}
return getSingleOpenTest(openUUID, verboseLevel);
}
/**
* Gets the JSON-Representation of all open-data-fields for one specific
* open-test-uuid
* @param openTestUUID
* @return the json-string
*
*
* columns in csv ("open data")
* 1:open_uuid,
* 2:open_test_uuid,
* 3:time,
* 4:cat_technology,
* 5:network_type,
* 6:lat,
* 7:long,
* 8:loc_src,
* 9:zip_code,
* 9a:gkz,
* 10:download_kbit,
* 11:upload_kbit,
* 12:ping_ms,
* 13:signal_strength,
* 14:server_name,
* 15:test_duration,
* 16:num_threads,
* 17:platform,
* 18:model,
* 19:client_version,
* 20:network_mcc_mnc,
* 21:network_name,
* 22:sim_mcc_mnc,
* 23:connection,
* 24:asn,
* 25:ip_anonym,
* 26:ndt_download_kbit,
* 27:ndt_upload_kbit,
* 28:implausible,
* 29:lte_rsrp
*
*
* Columns in test table
* uid (internal)
* uuid (private)
* client_id
* client_version
* client_name
* client_language (private)
* token (private, obsolete)
* server_id
* port
* use_ssl *
* time
* speed_upload
* speed_download
* ping_shortest
* encryption *
* client_public_ip (private)
* plattform
* os_version (internal)
* api_level (internal)
* device
* model
* product
* phone_type (internal)
* data_state (internal)
* network_country (internal)
* network_operator
* network_operator_name
* network_sim_country (internal)
* network_sim_operator
* network_sim_operator_name
* wifi_ssid (private)
* wifi_bssid (private)
* wifi_network_id (private)
* duration
* num_threads
* status
* timezone (private)
* bytes_download
* bytes_upload
* nsec_download
* nsec_upload
* server_ip
* client_software_version
* geo_lat
* geo_long
* network_type
* location
* signal_strength
* software_revision
* client_test_counter
* nat_type
* client_previous_test_status
* public_ip_asn
* speed_upload_log
* speed_download_log
* total_bytes_download
* total_bytes_upload
* wifi_link_speed
* public_ip_rdns
* public_ip_as_name
* test_slot
* provider_id
* network_is_roaming (internal)
* ping_shortest_log
* run_ndt (internal)
* num_threads_requested
* client_public_ip_anonymized
* zip_code
* gkz
* geo_provider
* geo_accuracy
* deleted (internal)
* comment (internal)
* open_uuid
* client_time (internal)
* zip_code_geo (internal)
* mobile_provider_id
* roaming_type
* open_test_uuid
* country_asn
* country_location
* test_if_bytes_download
* test_if_bytes_upload
* implausible
* testdl_if_bytes_download
* testdl_if_bytes_upload
* testul_if_bytes_download
* testul_if_bytes_upload
* country_geoip
* location_max_distance
* location_max_distance_gps
* network_group_name
* network_group_type
* time_dl_ns
* time_ul_ns
* num_threads_ul
* lte_rsrp
* lte_rsrq
* mobile_network_id
* mobile_sim_id
* dist_prev
* speed_prev
* tag
* ping_median
* ping_median_log
* client_ip_local_type (private)
*
* private: visible to user only (not public)
* internal: not visible (neither user nor public)
*
*/
private String getSingleOpenTest(String openTestUUID, int verboseLevel) {
final String sql = "SELECT t.uid as test_uid, " +
" ('P' || t.open_uuid) open_uuid," + //csv 1:open_uuid, UUID prefixed with 'P'
" ('O' || t.open_test_uuid) open_test_uuid," + //csv open_test_uuid, UUID prefixed with 'O'
" to_char(t.time AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS') \"time\"," +
" t.time full_time," + //csv: 3:time server time-stamp of start of measurement
" t.client_time client_time," + //(internal) client time-stamp of start of measure
" t.network_group_name cat_technology," + //csv 4:cat_technology
" t.network_group_type network_type," + //csv 5:network_type
/*
" t.geo_lat lat," + //csv 6:lat
" t.geo_long long," + // csv 7:long
" t.geo_provider loc_src," + //csv 8:loc_src android: 'gps'/'network'; browser/iOS: '' (empty string)
" t.geo_accuracy loc_accuracy, " + //accuracy of geo location in m
*/
//csv 6:lat
" (CASE WHEN (t.geo_accuracy < ?) AND (t.geo_provider != 'manual') AND (t.geo_provider != 'geocoder') THEN" +
" t.geo_lat" +
" WHEN (t.geo_accuracy < ?) THEN" +
" ROUND(t.geo_lat*1111)/1111" + // approx 100m
" ELSE null" +
" END) lat," +
// csv 7:long
" (CASE WHEN (t.geo_accuracy < ?) AND (t.geo_provider != 'manual') AND (t.geo_provider != 'geocoder') THEN" +
" t.geo_long" +
" WHEN (t.geo_accuracy < ?) THEN" +
" ROUND(t.geo_long*741)/741 " + //approx 100m
" ELSE null" +
" END) long," +
// csv 8:loc_src android: 'gps'/'network'; browser/iOS: '' (empty string)
" (CASE WHEN ((t.geo_provider = 'manual') OR (t.geo_provider = 'geocoder')) THEN" +
" 'rastered'" + //make raster transparent
" ELSE t.geo_provider" +
" END) loc_src," +
// accuracy of geo location in m
" (CASE WHEN (t.geo_accuracy < ?) AND (t.geo_provider != 'manual') AND (t.geo_provider != 'geocoder') " +
" THEN t.geo_accuracy " +
" WHEN (t.geo_accuracy < 100) AND ((t.geo_provider = 'manual') OR (t.geo_provider = 'geocoder')) THEN 100" + // limit accuracy to 100m
" WHEN (t.geo_accuracy < ?) THEN t.geo_accuracy" +
" ELSE null END) loc_accuracy, " +
//csv 9:zip-code - defined as integer in data base, only meaningful for measurements in Austria
" (CASE WHEN (t.zip_code < 1000 OR t.zip_code > 9999) THEN null ELSE t.zip_code END) zip_code," +
// " zip_code_geo," + //(internal) zip-code, integer derived from geo_location, Austria only
" t.gkz gkz," +
" k.gemeinde community," +
" k.bezirk district," +
" k.land province," +
" k.anhang cov800cat," +
" t.speed_download download_kbit," + //csv 10:download_kbit
" t.speed_upload upload_kbit," + //csv 11: upload_kbit
" t.wifi_link_speed," + // nominal speed of wifi-link in mbit/s , Android-only
" (t.ping_median::float / 1000000) ping_ms," + //median ping-time in ms (stored in ns in data base)
" signal_strength," + //csv 13:signal_strength RSSI, mainly GSM/UMTS and Wifi, Android only, in dBm
" lte_rsrp," + // csv 29: signal_strength RSRP, Android only, in dBm
" lte_rsrq," + // signal quality RSRQ, Android only, in dB
" ts.name server_name," + //csv 14:server_name, name of the test server used for download/upload (not applicable for JStest)
" implausible, " + //csv 28:implausible, measurement not shown in map nor used in statistics, normally not visible
" pinned, " + //true, if test is used in statistics, false if it is a repeated measurement
" public_ip_as_name, " + //name of AS (not number)
" duration test_duration," + //csv 15:test_duration, nominal duration of downlink and uplink throughput tests in seconds
" num_threads_requested," + // number of threads requested by control-server
" num_threads," + //csv 16:num_threads, number of threads used in downlink throughput test (uplink may differ)
" num_threads_ul," + // number of threads used in uplink test
" COALESCE(t.plattform, t.client_name) as platform," + //csv 17:platform; currently used: 'CLI'/'Android'/Applet/iOS/[from client_name: RMBTws, RMBTjs](null); (null) is used for RMBTjs
" COALESCE(adm.fullname, t.model) model," + //csv 18:model, translated t.model (model_native) to human readable form
" t.model model_native," + //device used for test; Android API 'model'; iOS:'product'; Browser: Browser-name (zB Firefox)
" t.product product," + // product used for test; Android APO 'product'; iOS: '' (not used); Browser: same as for model (browser name)
" t.client_software_version client_version," + //csv 19:client_version, SW-version of client software (not RMBT-client-version), eg. '1.3'
" t.network_operator network_mcc_mnc," + //csv 20:network_mcc_mnc, mobile country and network code of current network (Android only), string, eg "232-12'
" network_country," + //(internal) Android-only, country code derived by client from mobile network code
// " network_is_roaming," + //(internal) roaming-status of mobile network, boolean or (null); Android-only (obsolete)
" roaming_type," + //roaming-status of mobile network, integer: 0:not roaming,1:national,2:international,(null):unknown (eg. iOS)
" t.network_operator_name network_name," + //csv 21:network_name, name of current mobile network as displayed on device (eg: '3likeHome')
" t.network_sim_operator sim_mcc_mnc," + //csv 22:sim_mcc_mnc, home network of SIM (initial 5 digits from IMSI), eg '232-01'
" t.network_sim_country sim_country," + //(internal) Android-only, country derived by client from SIM (country of home network)
" COALESCE(mprov.name,msim.shortname,msim.name,prov.name) provider_name," + //pre-defined list of providers (based on provider_id) //TODO replace provider
" t.nat_type \"connection\"," + //csv 23:connection, translation-mechanism in NAT, eg. nat_local_to_public_ipv4
" t.public_ip_asn asn," + //csv 24:asn, AS (autonomous system) number, number of public IP network
" t.client_public_ip_anonymized ip_anonym," + //csv 25:ip_anonym, anonymized IP of client (IPv4: 8 bits removed, IPv6: 72 bits removed)
" (ndt.s2cspd*1000)::int ndt_download_kbit," + //csv 26:ndt_download_kbit, result of NDT downlink throughput test kbit/s
" (ndt.c2sspd*1000)::int ndt_upload_kbit," + //csv 27 ndt_uoload_kbit, result of NDT uplink throughput test in kbit/s
" country_geoip," + // country-code derived from public IP-address, eg. 'AT'
" country_location," + // country-code derived from geo_location, eg. 'DE'
" country_asn," + // country_code derived from AS, eg. 'EU'
" bytes_download," + // number of bytes downloaded during test (download and upload) (obsolete)
" bytes_upload," + // number of bytes uploaded during test (download and upload) (obsolete)
" test_if_bytes_download," + //downloaded bytes on interface during total test (inc. training, ping, without NDT) (obsolete)
" test_if_bytes_upload," + //uploaded bytes on interface during total test (inc. training, ping, without NDT) (obsolete)
" testdl_if_bytes_download," + //downloaded bytes on interface during download-test (without training-seq)
" testdl_if_bytes_upload," + //uploaded bytes on interface during download-test (without training-seq)
" testul_if_bytes_download," + //downloaded bytes on interface during upload-test (without training-seq)
" testul_if_bytes_upload," + //downloaded bytes on interface during upload-test (without training-seq)
" (t.nsec_download::float / 1000000) duration_download_ms," + //duration of download-test in ms
" (t.nsec_upload::float / 1000000) duration_upload_ms," + //duration of upload-test in ms
" (t.time_dl_ns::float / 1000000) time_dl_ms," + //relative start time of download-test in ms (ignoring training-phase)
" (t.time_ul_ns::float / 1000000) time_ul_ms," + //relative start time of download-test in ms (ignoring training-phase)
// " phone_type" + //(internal) radio type of phone: 0 no mobile radio, 1 GSM (incl. UMTS,LTE) 2 CDMA (obsolete)
" speed.items speed_items" + // json representation of individual up+down speed items
" FROM test t" +
" LEFT JOIN device_map adm ON adm.codename=t.model" +
" LEFT JOIN test_server ts ON ts.uid=t.server_id" +
" LEFT JOIN test_ndt ndt ON t.uid=ndt.test_id" +
" LEFT JOIN provider prov ON t.provider_id=prov.uid" +
" LEFT JOIN provider mprov ON t.mobile_provider_id=mprov.uid" +
" LEFT JOIN mccmnc2name msim ON t.mobile_sim_id=msim.uid" +
" LEFT JOIN kategorisierte_gemeinden k ON t.gkz=k.gemeinde_i" +
" LEFT JOIN speed ON speed.open_test_uuid=t.open_test_uuid" +
" WHERE " +
" t.deleted = false " +
" AND t.status = 'FINISHED' " +
" AND t.open_test_uuid = ? ";
//System.out.println(sql);
final String[] columns;
PreparedStatement ps = null;
ResultSet rs = null;
final JSONObject response = new JSONObject();
try
{
ps = conn.prepareStatement(sql);
//insert filter for accuracy
double accuracy = Double.parseDouble(settings.getString("RMBT_GEO_ACCURACY_DETAIL_LIMIT"));
ps.setDouble(1, accuracy);
ps.setDouble(2, accuracy);
ps.setDouble(3, accuracy);
ps.setDouble(4, accuracy);
ps.setDouble(5, accuracy);
ps.setDouble(6, accuracy);
//openTestIDs are starting with "O"
if (openTestUUID != null && openTestUUID.startsWith("O")) {
openTestUUID = openTestUUID.substring(1);
}
ps.setObject(7, openTestUUID,Types.OTHER);
if (!ps.execute())
return null;
rs = ps.getResultSet();
if (rs.next())
{
//fetch data for every field
for (int i=0;i<openDataFieldsFull.length;i++) {
//convert data to correct json response
final Object obj = rs.getObject(openDataFieldsFull[i]);
if (openDataBooleanFields.contains(openDataFieldsFull[i])) {
if (obj == null) {
response.put(openDataFieldsFull[i], false);
}
else {
response.put(openDataFieldsFull[i], obj);
}
}
else if (obj==null) {
response.put(openDataFieldsFull[i], JSONObject.NULL);
}
else if (openDataNumberFields.contains(openDataFieldsFull[i])) {
final String tmp = obj.toString().trim();
if (tmp.isEmpty())
response.put(openDataFieldsFull[i], JSONObject.NULL);
else
response.put(openDataFieldsFull[i], JSONObject.stringToValue(tmp));
}
else {
final String tmp = obj.toString().trim();
if (tmp.isEmpty())
response.put(openDataFieldsFull[i], JSONObject.NULL);
else
response.put(openDataFieldsFull[i], tmp);
}
}
//classify download, upload, ping, signal
response.put("download_classification", Classification.classify(Classification.THRESHOLD_DOWNLOAD, rs.getLong("download_kbit"), capabilities.getClassificationCapability().getCount()));
response.put("upload_classification", Classification.classify(Classification.THRESHOLD_UPLOAD, rs.getLong("upload_kbit"), capabilities.getClassificationCapability().getCount()));
response.put("ping_classification", Classification.classify(Classification.THRESHOLD_PING, rs.getLong("ping_ms")*1000000, capabilities.getClassificationCapability().getCount()));
//classify signal accordingly
if ((rs.getString("signal_strength") != null || rs
.getString("lte_rsrp") != null)
&& rs.getString("network_type") != null) { // signal available
if (rs.getString("lte_rsrp") == null) { // use RSSI
if (rs.getString("network_type").equals("WLAN")) { // RSSI for Wifi
response.put(
"signal_classification",
Classification
.classify(
Classification.THRESHOLD_SIGNAL_WIFI,
rs.getLong("signal_strength"), capabilities.getClassificationCapability().getCount()));
} else { // RSSI for Mobile
response.put(
"signal_classification",
Classification
.classify(
Classification.THRESHOLD_SIGNAL_MOBILE,
rs.getLong("signal_strength"), capabilities.getClassificationCapability().getCount()));
}
} else // RSRP for LTE
response.put("signal_classification", Classification
.classify(Classification.THRESHOLD_SIGNAL_RSRP,
rs.getLong("lte_rsrp"), capabilities.getClassificationCapability().getCount()));
} else { // no signal available
response.put("signal_classification", JSONObject.NULL);
}
//also load download/upload-speed-data, signal data and location data if possible
JSONObject speedCurve = new JSONObject();
JSONArray downloadSpeeds = new JSONArray();
JSONArray uploadSpeeds = new JSONArray();
JSONArray locArray = new JSONArray();
JSONArray signalArray = new JSONArray();
// speed data
final Gson gson = getGson(false);
final SpeedItems speedItems = gson.fromJson(rs.getString("speed_items"), SpeedItems.class);
if (speedItems != null)
{
long lastTime = -1;
for (SpeedItem item : speedItems.getAccumulatedSpeedItemsUpload()) {
JSONObject obj = new JSONObject();
final long time = Math.round((double)item.getTime() / 1000000);
if (time == lastTime)
continue;
obj.put("time_elapsed", time);
obj.put("bytes_total", item.getBytes());
uploadSpeeds.put(obj);
lastTime = time;
}
lastTime = -1;
for (SpeedItem item : speedItems.getAccumulatedSpeedItemsDownload()) {
JSONObject obj = new JSONObject();
final long time = Math.round((double)item.getTime() / 1000000);
if (time == lastTime)
continue;
obj.put("time_elapsed", time);
obj.put("bytes_total", item.getBytes());
downloadSpeeds.put(obj);
lastTime = time;
}
}
//if verbose - also add raw json data
if (speedItems != null && verboseLevel > 0) {
Map<String, Map<Integer, List<SpeedItem>>> rawJSON = speedItems.getRawJSONData();
JSONObject obj = new JSONObject();
//phases
for (String phase : rawJSON.keySet()) {
//threads
JSONObject threads = new JSONObject();
obj.put(phase, threads);
for (int thread : rawJSON.get(phase).keySet()) {
JSONArray threadItems = new JSONArray();
threads.put(Integer.toString(thread),threadItems);
//speed items
for (SpeedItem item : rawJSON.get(phase).get(thread)) {
JSONObject measurement = new JSONObject();
measurement.put("time_elapsed_ns", item.getTime());
measurement.put("bytes_total", item.getBytes());
threadItems.put(measurement);
}
}
}
response.put("speed_curve_threadwise", obj);
}
//Load signal strength from database
SignalGraph sigGraph = new SignalGraph(rs.getLong("test_uid"), rs.getTimestamp("client_time").getTime(), conn);
for (SignalGraph.SignalGraphItem item : sigGraph.getSignalList()) {
JSONObject json = new JSONObject();
json.put("time_elapsed",item.getTimeElapsed());
json.put("network_type", item.getNetworkType());
json.put("signal_strength", item.getSignalStrength());
json.put("lte_rsrp", item.getLteRsrp());
json.put("lte_rsrq", item.getLteRsrq());
json.put("cat_technology", item.getCatTechnology());
signalArray.put(json);
}
//Load gps coordinates from database
LocationGraph locGraph = new LocationGraph(rs.getLong("test_uid"), rs.getTimestamp("client_time").getTime(), conn);
double totalDistance = locGraph.getTotalDistance();
for (LocationGraph.LocationGraphItem item : locGraph.getLocations()) {
JSONObject json = new JSONObject();
json.put("time_elapsed",item.getTimeElapsed());
json.put("lat", item.getLatitude());
json.put("long", item.getLongitude());
json.put("loc_accuracy", (item.getAccuracy() > 0) ? item.getAccuracy() : JSONObject.NULL );
locArray.put(json);
}
speedCurve.put("upload", uploadSpeeds);
speedCurve.put("download", downloadSpeeds);
speedCurve.put("signal", signalArray);
speedCurve.put("location", locArray);
response.put("speed_curve", speedCurve);
//add total distance during test - but only if within bounds
if ((totalDistance > 0) &&
totalDistance <= Double.parseDouble(settings.getString("RMBT_GEO_DISTANCE_DETAIL_LIMIT")))
response.put("distance", totalDistance);
else
response.put("distance", JSONObject.NULL);
} else {
//invalid open_uuid
setStatus(Status.CLIENT_ERROR_NOT_FOUND);
response.put("error","invalid open-uuid");
}
}
catch (final JSONException e) {
Logger.getLogger(OpenTestResource.class.getName()).log(Level.SEVERE, null, e);
} catch (SQLException ex) {
try {
setStatus(Status.CLIENT_ERROR_NOT_FOUND);
response.put("error","invalid open-uuid");
} catch (JSONException ex1) {
Logger.getLogger(OpenTestResource.class.getName()).log(Level.SEVERE, null, ex1);
}
Logger.getLogger(OpenTestResource.class.getName()).log(Level.SEVERE, null, ex);
}
finally
{
try
{
if (rs != null)
rs.close();
if (ps != null)
ps.close();
}
catch (final SQLException e)
{
Logger.getLogger(OpenTestResource.class.getName()).log(Level.SEVERE, null, e);
}
}
return response.toString();
}
/**
* Calculate the rough distance in meters between two points
* taken from http://stackoverflow.com/questions/120283/working-with-latitude-longitude-values-in-java
* @param lat1
* @param lng1
* @param lat2
* @param lng2
* @return
*/
private static double distFrom(double lat1, double lng1, double lat2, double lng2) {
double earthRadius = 6371000;
double dLat = Math.toRadians(lat2-lat1);
double dLng = Math.toRadians(lng2-lng1);
double sindLat = Math.sin(dLat / 2);
double sindLng = Math.sin(dLng / 2);
double a = Math.pow(sindLat, 2) + Math.pow(sindLng, 2)
* Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2));
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
double dist = earthRadius * c;
return dist;
}
public static class LocationGraph {
private double totalDistance;
private ArrayList<LocationGraphItem> locations = new ArrayList<>();
/**
* Gets all distinctive locations of a client during a test
* @param testUID the uid of the test
* @param testTime the begin of the test
* @throws SQLException
*/
public LocationGraph(long testUID, long testTime, java.sql.Connection conn) throws SQLException {
PreparedStatement psLocation = conn.prepareStatement("SELECT test_id, g.geo_lat lat, g.geo_long long, g.accuracy loc_accuracy, time "
+ "FROM geo_location g "
+ "WHERE g.test_id = ? and provider != 'network' " //do not mix with network-locations (iOS: provider = '')
+ "ORDER BY time;");
psLocation.setLong(1, testUID);
ResultSet rsLocation = psLocation.executeQuery();
boolean first = true;
boolean usedCurrentItem = false;
LocationGraphItem item = null;
double lastLat=0;
double lastLong=0;
double lastAcc=0;
this.totalDistance=0;
while (rsLocation.next()) {
long timeElapsed = rsLocation.getTimestamp("time").getTime() - testTime;
//there could be measurements taken before a test started
//in this case, only return the last one
if (first && timeElapsed > 0 && item != null) {
this.locations.add(item);
lastLat = item.getLatitude();
lastLong = item.getLongitude();
lastAcc = item.getAccuracy();
first = false;
}
item = new LocationGraphItem(Math.max(timeElapsed,0), rsLocation.getDouble("long"), rsLocation.getDouble("lat"), rsLocation.getDouble("loc_accuracy"));
usedCurrentItem = false;
//put triplet in the array if it is not the first one
if (!first) {
//only put the point in the resulting array, if there is a significant
//distance from the last point
//therefore (difference in m) > (tolerance last point + tolerance new point)
double diff = OpenTestResource.distFrom(lastLat, lastLong, item.getLatitude(), item.getLongitude());
//System.out.println("dist: " + diff);
double maxDiff = item.getAccuracy() + lastAcc;
//System.out.println("Distance: " + diff + "; tolTotal " + maxDiff + "; tol1 " + lastAcc + "; tol2 " + json.getDouble("loc_accuracy"));
if (diff > maxDiff) {
this.locations.add(item);
lastLat = item.getLatitude();
lastLong = item.getLongitude();
lastAcc = item.getAccuracy();
this.totalDistance += diff;
}
else {
//if not, replace the old point, if the new is more accurate
if (item.getAccuracy() < lastAcc) {
this.locations.remove(this.locations.size()-1);
this.locations.add(item);
lastLat = item.getLatitude();
lastLong = item.getLongitude();
lastAcc = item.getAccuracy();
}
}
usedCurrentItem = true;
}
}
//always use the last item to connect the path with the end point
if (!usedCurrentItem && this.locations.size() > 0) {
//replace the last set point with it
//since it is in the same inaccurracy area
this.locations.remove(this.locations.size()-1);
this.locations.add(item);
lastLat = item.getLatitude();
lastLong = item.getLongitude();
lastAcc = item.getAccuracy();
}
//System.out.println("called w id: " + testUID + ", ct: " + testTime + ", d: " + this.totalDistance + ", " + this.getTotalDistance());
rsLocation.close();
psLocation.close();
}
public double getTotalDistance() {
return this.totalDistance;
}
public ArrayList<LocationGraphItem> getLocations() {
return this.locations;
}
private class LocationGraphItem {
private double longitude;
private double latitude;
private double accuracy;
private long timeElapsed;
public LocationGraphItem(long timeElapsed, double longitude, double latitude, double accuracy) {
this.longitude = longitude;
this.latitude = latitude;
this.timeElapsed = timeElapsed;
this.accuracy = accuracy;
}
/**
* @return The time elapsed since the begin of the test
*/
public long getTimeElapsed() {
return this.timeElapsed;
}
public double getLongitude() {
return this.longitude;
}
public double getLatitude() {
return this.latitude;
}
/**
* @return The accuracy of the measurement in meters
*/
public double getAccuracy() {
return this.accuracy;
}
}
}
public static class SignalGraph {
private static int LOWER_BOUND = -1500;
private static int MAX_TIME = 60000;
private ArrayList<SignalGraphItem> signalList = new ArrayList<>();
/**
* Gets all signal measurements from a test
* @param testUID the test uid
* @param testTime the begin of the test
* @throws SQLException
*/
public SignalGraph(long testUID, long testTime, java.sql.Connection conn) throws SQLException {
PreparedStatement psSignal = conn.prepareStatement("SELECT test_id, nt.name network_type, nt.group_name cat_technology, signal_strength, lte_rsrp, lte_rsrq, wifi_rssi, time "
+ "FROM signal "
+ "JOIN network_type nt "
+ "ON nt.uid = network_type_id "
+ "WHERE test_id = ? "
+ "ORDER BY time;");
psSignal.setLong(1, testUID);
ResultSet rsSignal = psSignal.executeQuery();
boolean first = true;
SignalGraphItem item = null;
while (rsSignal.next()) {
long timeElapsed = rsSignal.getTimestamp("time").getTime() - testTime;
//there could be measurements taken before a test started
//in this case, only return the last one
if (first && timeElapsed > 0 && item != null) {
this.signalList.add(item);
first = false;
}
//ignore measurements after a threshold of one minute
if (timeElapsed > MAX_TIME)
break;
int signalStrength = rsSignal.getInt("signal_strength");
int lteRsrp = rsSignal.getInt("lte_rsrp");
int lteRsrq = rsSignal.getInt("lte_rsrq");
if (signalStrength == 0)
signalStrength = rsSignal.getInt("wifi_rssi");
if (signalStrength > LOWER_BOUND)
item = new SignalGraphItem(Math.max(timeElapsed,0), rsSignal.getString("network_type"), signalStrength, lteRsrp, lteRsrq, rsSignal.getString("cat_technology"));
//put 5-let in the array if it is not the first one
if (!first || rsSignal.isLast()) {
if (timeElapsed < 0) {
item.timeElapsed = 1000;
}
this.signalList.add(item);
}
}
rsSignal.close();
psSignal.close();
}
public ArrayList<SignalGraphItem>getSignalList() {
return this.signalList;
}
private class SignalGraphItem {
private long timeElapsed;
private String networkType;
private int signalStrength;
private int lteRsrp;
private int lteRsrq;
private String catTechnology;
public SignalGraphItem(long timeElapsed, String networkType, int signalStrength, int lteRsrp, int lteRsrq, String catTechnology) {
this.timeElapsed = timeElapsed;
this.networkType = networkType;
this.signalStrength = signalStrength;
this.lteRsrp = lteRsrp;
this.lteRsrq = lteRsrq;
this.catTechnology = catTechnology;
}
/**
* @return The time elapsed since the begin of the test
*/
public long getTimeElapsed() {
return this.timeElapsed;
}
/**
* @return The type of the network, e.g.
*/
public String getNetworkType() {
return this.networkType;
}
/**
* @return The signal strength RSSI in dBm
*/
public int getSignalStrength() {
return this.signalStrength;
}
/**
* @return The signal strength RSRP in dBm
*/
public int getLteRsrp() {
return this.lteRsrp;
}
/**
* @return The signal quality RSRQ in dB
*/
public int getLteRsrq() {
return this.lteRsrq;
}
public String getCatTechnology() {
return this.catTechnology;
}
}
}
}