Implemented geolocation graph endpoint

This commit is contained in:
Rsl1122 2019-07-12 18:55:21 +03:00
parent f9bfb35a94
commit 49ea2fbfe9
14 changed files with 180 additions and 47 deletions

View File

@ -25,6 +25,7 @@ import com.djrapitops.plan.data.store.mutators.TPSMutator;
import com.djrapitops.plan.data.store.mutators.health.NetworkHealthInformation;
import com.djrapitops.plan.db.Database;
import com.djrapitops.plan.db.access.queries.ServerAggregateQueries;
import com.djrapitops.plan.db.access.queries.objects.GeoInfoQueries;
import com.djrapitops.plan.db.access.queries.objects.TPSQueries;
import com.djrapitops.plan.system.database.DBSystem;
import com.djrapitops.plan.system.info.server.Server;
@ -171,7 +172,7 @@ public class NetworkContainer extends DynamicDataContainer {
private void addPlayerInformation() {
putSupplier(NetworkKeys.PLAYERS_TOTAL, () -> getUnsafe(NetworkKeys.PLAYERS_MUTATOR).count());
putSupplier(NetworkKeys.WORLD_MAP_SERIES, () ->
graphs.special().worldMap(database.query(ServerAggregateQueries.networkGeolocationCounts())).toHighChartsSeries()
graphs.special().worldMap(database.query(GeoInfoQueries.networkGeolocationCounts())).toHighChartsSeries()
);
Key<BarGraph> geolocationBarChart = new Key<>(BarGraph.class, "GEOLOCATION_BAR_GRAPH");
putSupplier(geolocationBarChart, () -> graphs.bar().geolocationBarGraph(getUnsafe(NetworkKeys.PLAYERS_MUTATOR)));

View File

@ -19,7 +19,10 @@ package com.djrapitops.plan.db.access.queries;
import com.djrapitops.plan.db.access.Query;
import com.djrapitops.plan.db.access.QueryAllStatement;
import com.djrapitops.plan.db.access.QueryStatement;
import com.djrapitops.plan.db.sql.tables.*;
import com.djrapitops.plan.db.sql.tables.CommandUseTable;
import com.djrapitops.plan.db.sql.tables.ServerTable;
import com.djrapitops.plan.db.sql.tables.UserInfoTable;
import com.djrapitops.plan.db.sql.tables.UsersTable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
@ -132,33 +135,4 @@ public class ServerAggregateQueries {
};
}
public static Query<Map<String, Integer>> networkGeolocationCounts() {
String subQuery1 = SELECT +
GeoInfoTable.USER_UUID + ',' +
GeoInfoTable.GEOLOCATION + ',' +
GeoInfoTable.LAST_USED +
FROM + GeoInfoTable.TABLE_NAME;
String subQuery2 = SELECT +
GeoInfoTable.USER_UUID + ',' +
"MAX(" + GeoInfoTable.LAST_USED + ") as m" +
FROM + GeoInfoTable.TABLE_NAME +
GROUP_BY + GeoInfoTable.USER_UUID;
String sql = SELECT + GeoInfoTable.GEOLOCATION + ", COUNT(1) as c FROM (" +
'(' + subQuery1 + ") AS q1" +
" INNER JOIN (" + subQuery2 + ") AS q2 ON q1.uuid = q2.uuid)" +
WHERE + GeoInfoTable.LAST_USED + "=m" +
GROUP_BY + GeoInfoTable.GEOLOCATION;
return new QueryAllStatement<Map<String, Integer>>(sql) {
@Override
public Map<String, Integer> processResults(ResultSet set) throws SQLException {
Map<String, Integer> geolocationCounts = new HashMap<>();
while (set.next()) {
geolocationCounts.put(set.getString(GeoInfoTable.GEOLOCATION), set.getInt("c"));
}
return geolocationCounts;
}
};
}
}

View File

@ -130,4 +130,69 @@ public class GeoInfoQueries {
}
};
}
public static Query<Map<String, Integer>> networkGeolocationCounts() {
String subQuery1 = SELECT +
GeoInfoTable.USER_UUID + ", " +
GeoInfoTable.GEOLOCATION + ", " +
GeoInfoTable.LAST_USED +
FROM + GeoInfoTable.TABLE_NAME;
String subQuery2 = SELECT +
GeoInfoTable.USER_UUID + ", " +
"MAX(" + GeoInfoTable.LAST_USED + ") as m" +
FROM + GeoInfoTable.TABLE_NAME +
GROUP_BY + GeoInfoTable.USER_UUID;
String sql = SELECT + GeoInfoTable.GEOLOCATION + ", COUNT(1) as c FROM (" +
"(" + subQuery1 + ") AS q1" +
INNER_JOIN + "(" + subQuery2 + ") AS q2 ON q1.uuid = q2.uuid)" +
WHERE + GeoInfoTable.LAST_USED + "=m" +
GROUP_BY + GeoInfoTable.GEOLOCATION;
return new QueryAllStatement<Map<String, Integer>>(sql) {
@Override
public Map<String, Integer> processResults(ResultSet set) throws SQLException {
Map<String, Integer> geolocationCounts = new HashMap<>();
while (set.next()) {
geolocationCounts.put(set.getString(GeoInfoTable.GEOLOCATION), set.getInt("c"));
}
return geolocationCounts;
}
};
}
public static Query<Map<String, Integer>> serverGeolocationCounts(UUID serverUUID) {
String subQuery1 = SELECT +
GeoInfoTable.USER_UUID + ", " +
GeoInfoTable.GEOLOCATION + ", " +
GeoInfoTable.LAST_USED +
FROM + GeoInfoTable.TABLE_NAME;
String subQuery2 = SELECT +
GeoInfoTable.USER_UUID + ", " +
"MAX(" + GeoInfoTable.LAST_USED + ") as m" +
FROM + GeoInfoTable.TABLE_NAME +
GROUP_BY + GeoInfoTable.USER_UUID;
String sql = SELECT + GeoInfoTable.GEOLOCATION + ", COUNT(1) as c FROM (" +
"(" + subQuery1 + ") AS q1" +
INNER_JOIN + "(" + subQuery2 + ") AS q2 ON q1.uuid = q2.uuid" +
INNER_JOIN + UserInfoTable.TABLE_NAME + " u on u." + UserInfoTable.USER_UUID + "=q1.uuid)" +
WHERE + GeoInfoTable.LAST_USED + "=m" +
AND + "u." + UserInfoTable.SERVER_UUID + "=?" +
GROUP_BY + GeoInfoTable.GEOLOCATION;
return new QueryStatement<Map<String, Integer>>(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, serverUUID.toString());
}
@Override
public Map<String, Integer> processResults(ResultSet set) throws SQLException {
Map<String, Integer> geolocationCounts = new HashMap<>();
while (set.next()) {
geolocationCounts.put(set.getString(GeoInfoTable.GEOLOCATION), set.getInt("c"));
}
return geolocationCounts;
}
};
}
}

View File

@ -25,6 +25,7 @@ import com.djrapitops.plan.data.time.WorldTimes;
import com.djrapitops.plan.db.Database;
import com.djrapitops.plan.db.access.queries.analysis.ActivityIndexQueries;
import com.djrapitops.plan.db.access.queries.containers.ServerPlayerContainersQuery;
import com.djrapitops.plan.db.access.queries.objects.GeoInfoQueries;
import com.djrapitops.plan.db.access.queries.objects.SessionQueries;
import com.djrapitops.plan.db.access.queries.objects.TPSQueries;
import com.djrapitops.plan.db.access.queries.objects.WorldTimesQueries;
@ -32,9 +33,11 @@ import com.djrapitops.plan.system.database.DBSystem;
import com.djrapitops.plan.system.settings.config.PlanConfig;
import com.djrapitops.plan.system.settings.paths.TimeSettings;
import com.djrapitops.plan.utilities.html.graphs.Graphs;
import com.djrapitops.plan.utilities.html.graphs.bar.BarGraph;
import com.djrapitops.plan.utilities.html.graphs.line.LineGraphFactory;
import com.djrapitops.plan.utilities.html.graphs.pie.Pie;
import com.djrapitops.plan.utilities.html.graphs.pie.WorldPie;
import com.djrapitops.plan.utilities.html.graphs.special.WorldMap;
import com.djrapitops.plan.utilities.html.graphs.stack.StackGraph;
import com.djrapitops.plugin.api.TimeAmount;
@ -145,4 +148,17 @@ public class GraphJSONParser {
dataMap.put("activity_pie_series", activityPie.getSlices());
return dataMap;
}
public Map<String, Object> geolocationGraphsJSONAsMap(UUID serverUUID) {
Database db = dbSystem.getDatabase();
Map<String, Integer> geolocationCounts = db.query(GeoInfoQueries.serverGeolocationCounts(serverUUID));
BarGraph geolocationBarGraph = graphs.bar().geolocationBarGraph(geolocationCounts);
WorldMap worldMap = graphs.special().worldMap(geolocationCounts);
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("geolocation_series", worldMap.getEntries());
dataMap.put("geolocation_bar_series", geolocationBarGraph.getBars());
return dataMap;
}
}

View File

@ -72,6 +72,8 @@ public class GraphsJSONHandler implements PageHandler {
return new JSONResponse<>(graphJSON.serverWorldPieJSONAsMap(serverUUID));
case "activity":
return new JSONResponse<>(graphJSON.activityGraphsJSONAsMap(serverUUID));
case "geolocation":
return new JSONResponse<>(graphJSON.geolocationGraphsJSONAsMap(serverUUID));
default:
throw new BadRequestException("unknown 'type' parameter: " + type);
}

View File

@ -29,6 +29,10 @@ public class BarGraph implements HighChart {
this.bars = bars;
}
public List<Bar> getBars() {
return bars;
}
public String toHighChartsCategories() {
TextStringBuilder categories = new TextStringBuilder("[");
categories.appendWithSeparators(bars.stream().map(bar -> "'" + bar.getLabel() + "'").iterator(), ",");

View File

@ -20,6 +20,7 @@ import com.djrapitops.plan.data.store.mutators.PlayersMutator;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Map;
/**
* Factory class for Bar Graphs.
@ -36,4 +37,8 @@ public class BarGraphFactory {
public BarGraph geolocationBarGraph(PlayersMutator playersMutator) {
return new GeolocationBarGraph(playersMutator);
}
public BarGraph geolocationBarGraph(Map<String, Integer> geolocationCounts) {
return new GeolocationBarGraph(geolocationCounts);
}
}

View File

@ -30,20 +30,27 @@ public class GeolocationBarGraph extends BarGraph {
}
private GeolocationBarGraph(List<String> geolocations) {
super(turnToBars(geolocations));
super(turnToBars(toGeolocationCounts(geolocations)));
}
private static List<Bar> turnToBars(List<String> geolocations) {
Map<String, Integer> counts = new HashMap<>();
GeolocationBarGraph(Map<String, Integer> geolocationCounts) {
super(turnToBars(geolocationCounts));
}
for (String geolocation : geolocations) {
counts.put(geolocation, counts.getOrDefault(geolocation, 0) + 1);
}
return counts.entrySet().stream()
private static List<Bar> turnToBars(Map<String, Integer> geolocationCounts) {
return geolocationCounts.entrySet().stream()
.map(entry -> new Bar(entry.getKey(), entry.getValue()))
.sorted()
.limit(20L)
.collect(Collectors.toList());
}
private static Map<String, Integer> toGeolocationCounts(List<String> geolocations) {
Map<String, Integer> counts = new HashMap<>();
for (String geolocation : geolocations) {
counts.put(geolocation, counts.getOrDefault(geolocation, 0) + 1);
}
return counts;
}
}

View File

@ -42,7 +42,7 @@ public class SpecialGraphFactory {
return new PunchCard(sessions);
}
public HighChart worldMap(Map<String, Integer> geolocationCounts) {
public WorldMap worldMap(Map<String, Integer> geolocationCounts) {
return new WorldMap(geolocationCounts);
}

View File

@ -23,6 +23,7 @@ import org.apache.commons.text.TextStringBuilder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* World Map that uses iso-a3 specification of Country codes.
@ -99,4 +100,29 @@ public class WorldMap implements HighChart {
return dataBuilder.append("]").toString();
}
public List<Entry> getEntries() {
return geoCodeCounts.entrySet().stream()
.filter(entry -> entry.getValue() != 0)
.map(e -> new Entry(e.getKey(), e.getValue()))
.collect(Collectors.toList());
}
public static class Entry {
private String code;
private int value;
public Entry(String code, int value) {
this.code = code;
this.value = value;
}
public String getCode() {
return code;
}
public int getValue() {
return value;
}
}
}

View File

@ -895,8 +895,13 @@
class="fas fa-fw fa-globe col-green"></i>
Geolocations</h6>
</div>
<div class="chart-area">
<span class="loader"></span>
<div class="chart-area row">
<div class="col-xs-12 col-sm-12 col-md-3 col-lg-3">
<div id="countryBarChart"><span class="loader"></span></div>
</div>
<div class="col-xs-12 col-sm-12 col-md-9 col-lg-9">
<div id="worldMap"><span class="loader"></span></div>
</div>
</div>
</div>
</div>
@ -1641,6 +1646,33 @@
}
});
jsonRequest("../v1/graph?type=geolocation&serverName=${serverName}", function (json, error) {
if (json) {
var geolocationSeries = {
name: 'Players',
type: 'map',
mapData: Highcharts.maps['custom/world'],
data: json.geolocation_series,
joinBy: ['iso-a3', 'code']
};
var geolocationBarSeries = {
color: '#4CAF50',
name: 'Players',
data: json.geolocation_bar_series.map(function (bar) {
return bar.value
})
};
var geolocationBarCategories = json.geolocation_bar_series.map(function (bar) {
return bar.label
});
worldMap('worldMap', v.colors.geolocationsLow, v.colors.geolocationsHigh, geolocationSeries);
horizontalBarChart('countryBarChart', geolocationBarCategories, [geolocationBarSeries], 'Players');
} else if (error) {
$('#worldMap').text("Failed to load graph data: " + error);
$('#countryBarChart').text("Failed to load graph data: " + error);
}
});
jsonRequest("../v1/graph?type=uniqueAndNew&serverName=${serverName}", function (data, error) {
if (data) {
var uniquePlayers = {

View File

@ -17,7 +17,7 @@
package com.djrapitops.plan.db;
import com.djrapitops.plan.data.container.GeoInfo;
import com.djrapitops.plan.db.access.queries.ServerAggregateQueries;
import com.djrapitops.plan.db.access.queries.objects.GeoInfoQueries;
import com.djrapitops.plan.db.access.transactions.events.PlayerRegisterTransaction;
import com.djrapitops.plan.system.PlanSystem;
import org.junit.jupiter.api.AfterAll;
@ -108,7 +108,7 @@ class MySQLTest implements DatabaseTest {
saveGeoInfo(fifthUuid, new GeoInfo("-", "Not Known", 0));
saveGeoInfo(sixthUuid, new GeoInfo("-", "Local Machine", 0));
Map<String, Integer> got = database.query(ServerAggregateQueries.networkGeolocationCounts());
Map<String, Integer> got = database.query(GeoInfoQueries.networkGeolocationCounts());
Map<String, Integer> expected = new HashMap<>();
// first user has a more recent connection from Finland so their country should be counted as Finland.

View File

@ -17,7 +17,7 @@
package com.djrapitops.plan.db;
import com.djrapitops.plan.data.container.GeoInfo;
import com.djrapitops.plan.db.access.queries.ServerAggregateQueries;
import com.djrapitops.plan.db.access.queries.objects.GeoInfoQueries;
import com.djrapitops.plan.db.access.queries.objects.ServerQueries;
import com.djrapitops.plan.db.access.transactions.StoreServerInformationTransaction;
import com.djrapitops.plan.db.access.transactions.events.PlayerRegisterTransaction;
@ -136,7 +136,7 @@ public class SQLiteTest implements DatabaseTest {
saveGeoInfo(fifthUuid, new GeoInfo("-", "Not Known", 0));
saveGeoInfo(sixthUuid, new GeoInfo("-", "Local Machine", 0));
Map<String, Integer> got = database.query(ServerAggregateQueries.networkGeolocationCounts());
Map<String, Integer> got = database.query(GeoInfoQueries.networkGeolocationCounts());
Map<String, Integer> expected = new HashMap<>();
// first user has a more recent connection from Finland so their country should be counted as Finland.

View File

@ -87,4 +87,5 @@ Type | Description
`uniqueAndNew` | Player data points for each day, how many unique and how many new players were there each day. Last 180 days
`calendar` | Calendar data points for each day there is data for. Last 2 years.
`worldPie` | World Pie data of all sessions combined
`activity` | Activity stack graph and pie graph data
`activity` | Activity stack graph and pie graph data
`geolocation` | World Map and bar graph data. World Map uses iso-a3 specification of Country codes and Bar graph uses country names.